最新服务器上的版本,以后用这个
wangzhenxin
2023-11-19 bc164b8bdbfbdf1d8229a5ced6b08d7cb8db7361
commit | author | age
2207d6 1 # Parallel Execution
W 2
3 When execution time of your tests is longer than a coffee break, it is a good reason to think about making your tests faster. If you have already tried to run them on SSD drive, or to use PhantomJS instead of Selenium, and the execution time still upsets you, it might be a good idea to run your tests in parallel.
4
5 ## Where to start
6
7 Codeception does not provide a command like `run-parallel`. There is no common solution that can play well for everyone. Here are the questions you will need to answer:
8
9 * How parallel processes will be executed?
10 * How parallel processes won't affect each other?
11 * Will they use different databases?
12 * Will they use different hosts?
13 * How should I split my tests across parallel processes?
14
15 There are two approaches to achieve parallelization. We can use [Docker](http://docker.com) and run each process inside isolated containers, and have those containers executed simultaneously.
16
17 Docker works really well for isolating testing environments.
18 By the time of writing this chapter, we didn't have an awesome tool like it. This chapter demonstrates how to manage parallel execution manually. As you will see we spend too much effort trying to isolate tests which Docker does for free. Today we <strong>recommend using Docker</strong> for parallel testing.
19
20 ## Docker
21
22 Please make sure you have `docker` or [Docker Toolbox](https://www.docker.com/products/docker-toolbox) installed. Docker experience is required as well.
23
24 ### Using Codeception Docker image
25
26 Run official Codeception image from DockerHub:
27
28     docker run codeception/codeception
29
30 Running tests from a project, by mounting the current path as a host-volume into the container.
31 The **default working directory in the container is `/project`**.
32
33     docker run -v ${PWD}:/project codeception/codeception run
34
35 To prepare application and tests to be executed inside containers you will need to use [Docker Compose](https://docs.docker.com/compose/) to run multiple containers and connect them together.
36
37 Define all required services in `docker-compose.yml` file. Make sure to follow Docker philisophy: 1 service = 1 container. So each process should be defined as its own service. Those services can use official Docker images pulled from DockerHub. Directories with code and tests should be mounted using `volume` directive. And exposed ports should be explicitly set using `ports` directive.
38
39 We prepared a sample config with codeception, web server, database, and selenium with firefox to be executed together.
40
41 ```yaml
42 version: '3'
43 services:
44   codecept:
45     image: codeception/codeception
46     depends_on:
47       - chrome
48       - web
49     volumes:
50       - .:/project
51   web:
52     image: php:7-apache
53     depends_on:
54       - db
55     volumes:
56       - .:/var/www/html
57   db:
58     image: percona:5.6
59   chrome:
60     image: selenium/standalone-chrome
61 ```
62
63 Codeception service will execute command `codecept run` but only after all services are started. This is defined using `depends_on` parameter.
64
65 It is easy to add more custom services. For instance to use Redis you just simple add this lines:
66
67 ```yaml
68   redis:
69     image: redis:3
70 ```
71
72 By default the image has codecept as its entrypoint, to run the tests simply supply the run command
73
74 ```
75 docker-compose run --rm codecept help
76 ```
77
78 Run suite
79
80 ```
81 docker-compose run --rm codecept run acceptance
82 ```
83
84 ```
85 docker-compose run --rm codecept run acceptance LoginCest
86 ```
87
88 Development bash
89
90 ```
91 docker-compose run --rm --entrypoint bash codecept
92 ```
93
94 And finally to execute testing in parallel you should define how you split your tests and run parallel processes for `docker-compose`. Here we split tests by suites, but you can use different groups to split your tests. In section below you will learn how to do that with Robo.
95
96 ```
97 docker-compose --project-name test-web run -d --rm codecept run --html report-web.html web & \
98 docker-compose --project-name test-unit run -d --rm codecept run --html report-unit.html unit & \
99 docker-compose --project-name test-functional run -d --rm codecept run --html report-functional.html functional
100 ```
101
102 At the end, it is worth specifying that Docker setup can be complicated and please make sure you understand Docker and Docker Compose before proceed. We prepared some links that might help you:
103
104 * [Acceptance Tests Demo Repository](https://github.com/dmstr/docker-acception)
105 * [Dockerized Codeception Internal Tests](https://github.com/Codeception/Codeception/blob/master/tests/README.md#dockerized-testing)
106 * [Phundament App with Codeception](https://gist.github.com/schmunk42/d6893a64963509ff93daea80f722f694)
107
108 If you want to automate splitting tests by parallel processes, and executing them using PHP script you should use Robo task runner to do that.
109
110 ## Robo
111
112 ### What to do
113
114 Parallel Test Execution consists of 3 steps:
115
116 * splitting tests
117 * running tests in parallel
118 * merging results
119
120 We propose to perform those steps using a task runner. In this guide we will use [**Robo**](http://robo.li) task runner. It is a modern PHP task runner that is very easy to use. It uses [Symfony Process](http://symfony.com/doc/current/components/process.html) to spawn background and parallel processes. Just what we need for the step 2! What about steps 1 and 3? We have created robo [tasks](https://github.com/Codeception/robo-paracept) for splitting tests into groups and merging resulting JUnit XML reports.
121
122 To conclude, we need:
123
124 * [Robo](http://robo.li), a task runner.
125 * [robo-paracept](https://github.com/Codeception/robo-paracept) - Codeception tasks for parallel execution.
126
127 ## Preparing Robo and Robo-paracept
128
129 Execute this command in an empty folder to install Robo and Robo-paracept :
130 ```bash
131 $ composer require codeception/robo-paracept:dev-master
132 ```
133
134 You need to install Codeception after, if codeception is already installed it will not work.
135 ```bash
136 $ composer require codeception/codeception
137 ```
138
139 ### Preparing Robo
140
141 Initializes basic RoboFile in the root of your project
142
143 ```bash
144 $ robo init
145 ```
146
147 Open `RoboFile.php` to edit it
148
149 ```php
150 <?php
151
152 class RoboFile extends \Robo\Tasks
153 {
154     // define public methods as commands
155 }
156 ```
157
158 Each public method in robofile can be executed as a command from console. Let's define commands for 3 steps and include autoload.
159
160 ```php
161 <?php
162 require_once 'vendor/autoload.php';
163
164 class Robofile extends \Robo\Tasks
165 {
166     use \Codeception\Task\MergeReports;
167     use \Codeception\Task\SplitTestsByGroups;
168
169     public function parallelSplitTests()
170     {
171
172     }
173
174     public function parallelRun()
175     {
176
177     }
178
179     public function parallelMergeResults()
180     {
181
182     }
183 }
184 ```
185
186 If you run `robo`, you can see the respective commands:
187
188 ```bash
189 $ robo
190 Robo version 0.6.0
191
192 Usage:
193   command [options] [arguments]
194
195 Options:
196   -h, --help            Display this help message
197   -q, --quiet           Do not output any message
198   -V, --version         Display this application version
199       --ansi            Force ANSI output
200       --no-ansi         Disable ANSI output
201   -n, --no-interaction  Do not ask any interactive question
202   -v|vv|vvv, --verbose  Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
203
204 Available commands:
205   help                    Displays help for a command
206   list                    Lists commands
207  parallel
208   parallel:merge-results
209   parallel:run
210   parallel:split-tests
211 ```
212
213 #### Step 1: Split Tests
214
215 Codeception can organize tests into [groups](http://codeception.com/docs/07-AdvancedUsage#Groups). Starting from 2.0 it can load information about a group from a files. Sample text file with a list of file names can be treated as a dynamically configured group. Take a look into sample group file:
216
217 ```bash
218 tests/functional/LoginCept.php
219 tests/functional/AdminCest.php:createUser
220 tests/functional/AdminCest.php:deleteUser
221 ```
222
223 Tasks from `\Codeception\Task\SplitTestsByGroups` will generate non-intersecting group files.  You can either split your tests by files or by single tests:
224
225 ```php
226 <?php
227     function parallelSplitTests()
228     {
229         // Split your tests by files
230         $this->taskSplitTestFilesByGroups(5)
231             ->projectRoot('.')
232             ->testsFrom('tests/acceptance')
233             ->groupsTo('tests/_data/paracept_')
234             ->run();
235
236         /*
237         // Split your tests by single tests (alternatively)
238         $this->taskSplitTestsByGroups(5)
239             ->projectRoot('.')
240             ->testsFrom('tests/acceptance')
241             ->groupsTo('tests/_data/paracept_')
242             ->run();
243         */
244     }
245
246 ```
247
248 Let's prepare group files:
249
250 ```bash
251 $ robo parallel:split-tests
252
253  [Codeception\Task\SplitTestFilesByGroupsTask] Processing 33 files
254  [Codeception\Task\SplitTestFilesByGroupsTask] Writing tests/_data/paracept_1
255  [Codeception\Task\SplitTestFilesByGroupsTask] Writing tests/_data/paracept_2
256  [Codeception\Task\SplitTestFilesByGroupsTask] Writing tests/_data/paracept_3
257  [Codeception\Task\SplitTestFilesByGroupsTask] Writing tests/_data/paracept_4
258  [Codeception\Task\SplitTestFilesByGroupsTask] Writing tests/_data/paracept_5
259 ```
260
261 Now we have group files. We should update `codeception.yml` to load generated group files. In our case we have groups: *paracept_1*, *paracept_2*, *paracept_3*, *paracept_4*, *paracept_5*.
262
263 ```yaml
264 groups:
265     paracept_*: tests/_data/paracept_*
266 ```
267
268 Let's try to execute tests from the second group:
269
270 ```bash
271 $ codecept run acceptance -g paracept_2
272 ```
273
274 #### Step 2: Running Tests
275
276 Robo has `ParallelExec` task to spawn background processes.
277
278 ##### Inside Container
279
280 If you are using [Docker](#docker)  containers you can launch multiple Codeception containers for different groups:
281
282 ```php
283 public function parallelRun()
284 {
285     $parallel = $this->taskParallelExec();
286     for ($i = 1; $i <= 5; $i++) {
287         $parallel->process(
288             $this->taskExec('docker-compose run --rm codecept run')
289                 ->option('group', "paracept_$i") // run for groups paracept_*
290                 ->option('xml', "tests/_log/result_$i.xml") // provide xml report
291         );
292     }
293     return $parallel->run();
294 }
295 ```
296
297 ##### Locally
298
299 If you want to run tests locally just use preinstalled `taskCodecept` task of Robo to define Codeception commands and put them inside `parallelExec`.
300
301 ```php
302 <?php
303 public function parallelRun()
304 {
305     $parallel = $this->taskParallelExec();
306     for ($i = 1; $i <= 5; $i++) {
307         $parallel->process(
308             $this->taskCodecept() // use built-in Codecept task
309             ->suite('acceptance') // run acceptance tests
310             ->group("paracept_$i") // for all paracept_* groups
311             ->xml("tests/_log/result_$i.xml") // save XML results
312         );
313     }
314     return $parallel->run();
315 }
316 ```
317
318 In case you don't use containers you can isolate processes by starting different web servers and databases per each test process.
319
320 We can define different databases for different processes. This can be done using [Environments](http://codeception.com/docs/07-AdvancedUsage#Environments). Let's define 5 new environments in `acceptance.suite.yml`:
321
322 ```yaml
323 actor: AcceptanceTester
324 modules:
325     enabled:
326         - Db:
327             dsn: 'mysql:dbname=testdb;host=127.0.0.1'
328             user: 'root'
329             dump: 'tests/_data/dump.sql'
330             populate: true
331             cleanup: true
332         - WebDriver:
333             url: 'http://localhost/'
334 env:
335     env1:
336         modules:
337             config:
338                 Db:
339                     dsn: 'mysql:dbname=testdb_1;host=127.0.0.1'
340                 WebDriver:
341                     url: 'http://test1.localhost/'
342     env2:
343         modules:
344             config:
345                 Db:
346                     dsn: 'mysql:dbname=testdb_2;host=127.0.0.1'
347                 WebDriver:
348                     url: 'http://test2.localhost/'
349     env3:
350         modules:
351             config:
352                 Db:
353                     dsn: 'mysql:dbname=testdb_3;host=127.0.0.1'
354                 WebDriver:
355                     url: 'http://test3.localhost/'
356     env4:
357         modules:
358             config:
359                 Db:
360                     dsn: 'mysql:dbname=testdb_4;host=127.0.0.1'
361                 WebDriver:
362                     url: 'http://test4.localhost/'
363     env5:
364         modules:
365             config:
366                 Db:
367                     dsn: 'mysql:dbname=testdb_5;host=127.0.0.1'
368                 WebDriver:
369                     url: 'http://test5.localhost/'
370 ```
371
372 ----
373
374 After the `parallelRun` method is defined you can execute tests with
375
376 ```bash
377 $ robo parallel:run
378 ```
379
380 #### Step 3: Merge Results
381
382 In case of `parallelExec` task we recommend to save results as JUnit XML, which can be merged and plugged into Continuous Integration server.
383
384 ```php
385 <?php
386     function parallelMergeResults()
387     {
388         $merge = $this->taskMergeXmlReports();
389         for ($i=1; $i<=5; $i++) {
390             $merge->from("tests/_output/result_paracept_$i.xml");
391         }
392         $merge->into("tests/_output/result_paracept.xml")->run();
393     }
394
395 ```
396 Now, we can execute :
397 ```bash
398 $ robo parallel:merge-results
399 ```
400 `result_paracept.xml` file will be generated. It can be processed and analyzed.
401
402 #### All Together
403
404 To create one command to rule them all we can define new public method `parallelAll` and execute all commands. We will save the result of `parallelRun` and use it for our final exit code:
405
406 ```php
407 <?php
408     function parallelAll()
409     {
410         $this->parallelSplitTests();
411         $result = $this->parallelRun();
412         $this->parallelMergeResults();
413         return $result;
414     }
415
416 ```
417
418 ## Conclusion
419
420 Codeception does not provide tools for parallel test execution. This is a complex task and solutions may vary depending on a project. We use [Robo](http://robo.li) task runner as an external tool to perform all required steps. To prepare our tests to be executed in parallel we use Codeception features of dynamic groups and environments. To do even more we can create Extensions and Group classes to perform dynamic configuration depending on a test process.