最新服务器上的版本,以后用这个
wangzhenxin
2023-11-19 bc164b8bdbfbdf1d8229a5ced6b08d7cb8db7361
commit | author | age
2207d6 1 # Advanced Usage
W 2
3 In this chapter we will cover some techniques and options that you can use to improve your testing experience
4 and keep your project better organized.
5
6 ## Cest Classes
7
8 If you want to get a class-like structure for your Cepts, you can use the Cest format instead of plain PHP.
9 It is very simple and is fully compatible with Cept scenarios. It means that if you feel that your test is long enough
10 and you want to split it, you can easily move it into classes.
11
12 You can create a Cest file by running the command:
13
14 ```bash
15 $ php vendor/bin/codecept generate:cest suitename CestName
16 ```
17
18 The generated file will look like this:
19
20 ```php
21 <?php
22 class BasicCest
23 {
24     public function _before(\AcceptanceTester $I)
25     {
26     }
27
28     public function _after(\AcceptanceTester $I)
29     {
30     }
31
32     // tests
33     public function tryToTest(\AcceptanceTester $I)
34     {
35     }
36 }
37 ```
38
39 **Each public method of Cest (except those starting with `_`) will be executed as a test**
40 and will receive an instance of the Actor class as the first parameter and the `$scenario` variable as the second one.
41
42 In `_before` and `_after` methods you can use common setups and teardowns for the tests in the class.
43
44 As you see, we are passing the Actor object into `tryToTest` method. This allows us to write scenarios the way we did before:
45
46 ```php
47 <?php
48 class BasicCest
49 {
50     // test
51     public function tryToTest(\AcceptanceTester $I)
52     {
53         $I->amOnPage('/');
54         $I->click('Login');
55         $I->fillField('username', 'john');
56         $I->fillField('password', 'coltrane');
57         $I->click('Enter');
58         $I->see('Hello, John');
59         $I->seeInCurrentUrl('/account');
60     }
61 }
62 ```
63
64 As you see, Cest classes have no parents.
65 This is done intentionally. It allows you to extend your classes with common behaviors and workarounds
66 that may be used in child classes. But don't forget to make these methods `protected` so they won't be executed as tests.
67
68 Cest format also can contain hooks based on test results:
69
70 * `_failed` will be executed on failed test
71 * `_passed` will be executed on passed test
72
73 ```php
74 <?php
75 public function _failed(\AcceptanceTester $I)
76 {
77     // will be executed on test failure
78 }
79
80 public function _passed(\AcceptanceTester $I)
81 {
82     // will be executed when test is successful
83 }
84 ```
85
86 ## Dependency Injection
87
88 Codeception supports simple dependency injection for Cest and \Codeception\TestCase\Test classes.
89 It means that you can specify which classes you need as parameters of the special `_inject()` method,
90 and Codeception will automatically create the respective objects and invoke this method,
91 passing all dependencies as arguments. This may be useful when working with Helpers. Here's an example for Cest:
92
93 ```php
94 <?php
95 class SignUpCest
96 {
97     /**
98      * @var Helper\SignUp
99      */
100     protected $signUp;
101
102     /**
103      * @var Helper\NavBarHelper
104      */
105     protected $navBar;
106
107     protected function _inject(\Helper\SignUp $signUp, \Helper\NavBar $navBar)
108     {
109         $this->signUp = $signUp;
110         $this->navBar = $navBar;
111     }
112
113     public function signUp(\AcceptanceTester $I)
114     {
115         $this->navBar->click('Sign up');
116         $this->signUp->register([
117             'first_name'            => 'Joe',
118             'last_name'             => 'Jones',
119             'email'                 => 'joe@jones.com',
120             'password'              => '1234',
121             'password_confirmation' => '1234'
122         ]);
123     }
124 }
125 ```
126
127 And for Test classes:
128
129 ```php
130 <?php
131 class MathTest extends \Codeception\TestCase\Test
132 {
133    /**
134     * @var \UnitTester
135     */
136     protected $tester;
137
138     /**
139      * @var Helper\Math
140      */
141     protected $math;
142
143     protected function _inject(\Helper\Math $math)
144     {
145         $this->math = $math;
146     }
147
148     public function testAll()
149     {
150         $this->assertEquals(3, $this->math->add(1, 2));
151         $this->assertEquals(1, $this->math->subtract(3, 2));
152     }
153 }
154 ```
155
156 However, Dependency Injection is not limited to this. It allows you to **inject any class**,
157 which can be constructed with arguments known to Codeception.
158
159 In order to make auto-wiring work, you will need to implement the `_inject()` method with the list of desired arguments.
160 It is important to specify the type of arguments, so Codeception can guess which objects are expected to be received.
161 The `_inject()` will only be invoked once, just after creation of the TestCase object (either Cest or Test).
162 Dependency Injection will also work in a similar manner for Helper and Actor classes.
163
164 Each test of a Cest class can declare its own dependencies and receive them from method arguments:
165
166 ```php
167 <?php
168 class UserCest
169 {
170     function updateUser(\Helper\User $u, \AcceptanceTester $I, \Page\User $userPage)
171     {
172         $user = $u->createDummyUser();
173         $userPage->login($user->getName(), $user->getPassword());
174         $userPage->updateProfile(['name' => 'Bill']);
175         $I->see('Profile was saved');
176         $I->see('Profile of Bill','h1');
177     }
178 }
179 ```
180
181 Moreover, Codeception can resolve dependencies recursively (when `A` depends on `B`, and `B` depends on `C` etc.)
182 and handle parameters of primitive types with default values (like `$param = 'default'`).
183 Of course, you are not allowed to have *cyclic dependencies*.
184
185 ## Example Annotation
186
187 What if you want to execute the same test scenario with different data? In this case you can inject examples
188 as `\Codeception\Example` instances.
189 Data is defined via the `@example` annotation, using JSON or Doctrine-style notation (limited to a single line). Doctrine-style:
190
191 ```php
192 <?php
193 class EndpointCest
194 {
195  /**
196   * @example ["/api/", 200]
197   * @example ["/api/protected", 401]
198   * @example ["/api/not-found-url", 404]
199   * @example ["/api/faulty", 500]
200   */
201   public function checkEndpoints(ApiTester $I, \Codeception\Example $example)
202   {
203     $I->sendGET($example[0]);
204     $I->seeResponseCodeIs($example[1]);
205   }
206 }
207 ```
208
209 JSON:
210
211 ```php
212 <?php
213 class PageCest
214 {
215  /**
216   * @example { "url": "/", "title": "Welcome" }
217   * @example { "url": "/info", "title": "Info" }
218   * @example { "url": "/about", "title": "About Us" }
219   * @example { "url": "/contact", "title": "Contact Us" }
220   */
221   public function staticPages(AcceptanceTester $I, \Codeception\Example $example)
222   {
223     $I->amOnPage($example['url']);
224     $I->see($example['title'], 'h1');
225     $I->seeInTitle($example['title']);
226   }
227 }
228 ```
229
230 <div class="alert alert-info">
231 If you use JSON notation please keep in mind that all string keys
232 and values should be enclosed in double quotes (`"`) according to JSON standard.
233 </div>
234
235 Key-value data in Doctrine-style annotation syntax:
236
237 ```php
238 <?php
239 class PageCest
240 {
241  /**
242   * @example(url="/", title="Welcome")
243   * @example(url="/info", title="Info")
244   * @example(url="/about", title="About Us")
245   * @example(url="/contact", title="Contact Us")
246   */
247   public function staticPages(AcceptanceTester $I, \Codeception\Example $example)
248   {
249     $I->amOnPage($example['url']);
250     $I->see($example['title'], 'h1');
251     $I->seeInTitle($example['title']);
252   }
253 }
254 ```
255
256 ## DataProvider Annotations
257
258 You can also use the `@dataProvider` annotation for creating dynamic examples for [Cest classes](#Cest-Classes), using a **protected method** for providing example data:
259
260 ```php
261 <?php
262 class PageCest
263 {
264    /**
265     * @dataProvider pageProvider
266     */
267     public function staticPages(AcceptanceTester $I, \Codeception\Example $example)
268     {
269         $I->amOnPage($example['url']);
270         $I->see($example['title'], 'h1');
271         $I->seeInTitle($example['title']);
272     }
273
274     /**
275      * @return array
276      */
277     protected function pageProvider() // alternatively, if you want the function to be public, be sure to prefix it with `_`
278     {
279         return [
280             ['url'=>"/", 'title'=>"Welcome"],
281             ['url'=>"/info", 'title'=>"Info"],
282             ['url'=>"/about", 'title'=>"About Us"],
283             ['url'=>"/contact", 'title'=>"Contact Us"]
284         ];
285     }
286 }
287 ```
288
289 `@dataprovider` annotation is also available for [unit tests](https://codeception.com/docs/05-UnitTests), in this case the data provider **method must be public**.
290 For more details about how to use data provider for unit tests, please refer to [PHPUnit documentation](https://phpunit.de/manual/current/en/writing-tests-for-phpunit.html#writing-tests-for-phpunit.data-providers).
291
292 ## Before/After Annotations
293
294 You can control execution flow with `@before` and `@after` annotations. You may move common actions
295 into protected (non-test) methods and invoke them before or after the test method by putting them into annotations.
296 It is possible to invoke several methods by using more than one `@before` or `@after` annotation.
297 Methods are invoked in order from top to bottom.
298
299 ```php
300 <?php
301 class ModeratorCest {
302
303     protected function login(AcceptanceTester $I)
304     {
305         $I->amOnPage('/login');
306         $I->fillField('Username', 'miles');
307         $I->fillField('Password', 'davis');
308         $I->click('Login');
309     }
310
311     /**
312      * @before login
313      */
314     public function banUser(AcceptanceTester $I)
315     {
316         $I->amOnPage('/users/charlie-parker');
317         $I->see('Ban', '.button');
318         $I->click('Ban');
319     }
320
321     /**
322      * @before login
323      * @before cleanup
324      * @after logout
325      * @after close
326      */
327     public function addUser(AcceptanceTester $I)
328     {
329         $I->amOnPage('/users/charlie-parker');
330         $I->see('Ban', '.button');
331         $I->click('Ban');
332     }
333 }
334 ```
335
336 ## Environments
337
338 For cases where you need to run tests with different configurations you can define different config environments.
339 The most typical use cases are running acceptance tests in different browsers,
340 or running database tests using different database engines.
341
342 Let's demonstrate the usage of environments for the browsers case.
343
344 We need to add some new lines to `acceptance.suite.yml`:
345
346 ``` yaml
347 actor: AcceptanceTester
348 modules:
349     enabled:
350         - WebDriver
351         - \Helper\Acceptance
352     config:
353         WebDriver:
354             url: 'http://127.0.0.1:8000/'
355             browser: 'firefox'
356
357 env:
358     phantom:
359          modules:
360             config:
361                 WebDriver:
362                     browser: 'phantomjs'
363
364     chrome:
365          modules:
366             config:
367                 WebDriver:
368                     browser: 'chrome'
369
370     firefox:
371         # nothing changed
372 ```
373
374 Basically you can define different environments inside the `env` root, name them (`phantom`, `chrome` etc.),
375 and then redefine any configuration parameters that were set before.
376
377 You can also define environments in separate configuration files placed in the directory specified by the `envs` option in
378 the `paths` configuration:
379
380 ```yaml
381 paths:
382     envs: tests/_envs
383 ```
384
385 The names of these files are used as environments names
386 (e.g. `chrome.yml` or `chrome.dist.yml` for an environment named `chrome`).
387 You can generate a new file with this environment configuration by using the `generate:environment` command:
388
389 ```bash
390 $ php vendor/bin/codecept g:env chrome
391 ```
392
393 In that file you can specify just the options you wish to override:
394
395 ```yaml
396 modules:
397     config:
398         WebDriver:
399             browser: 'chrome'
400 ```
401
402 The environment configuration files are merged into the main configuration before the suite configuration is merged.
403
404 You can easily switch between those configs by running tests with `--env` option.
405 To run the tests only for PhantomJS you just need to pass `--env phantom` as an option:
406
407 ```bash
408 $ php vendor/bin/codecept run acceptance --env phantom
409 ```
410
411 To run the tests in all 3 browsers, list all the environments:
412
413 ```bash
414 $ php vendor/bin/codecept run acceptance --env phantom --env chrome --env firefox
415 ```
416
417 The tests will be executed 3 times, each time in a different browser.
418
419 It's also possible to merge multiple environments into a single configuration by separating them with a comma:
420
421 ```bash
422 $ php vendor/bin/codecept run acceptance --env dev,phantom --env dev,chrome --env dev,firefox
423 ```
424
425 The configuration is merged in the order given.
426 This way you can easily create multiple combinations of your environment configurations.
427
428 Depending on the environment, you may choose which tests are to be executed.
429 For example, you might need some tests to be executed in Firefox only, and some tests in Chrome only.
430
431 The desired environments can be specified with the `@env` annotation for tests in Test and Cest formats:
432
433 ```php
434 <?php
435 class UserCest
436 {
437     /**
438      * This test will be executed only in 'firefox' and 'phantom' environments
439      *
440      * @env firefox
441      * @env phantom
442      */
443     public function webkitOnlyTest(AcceptanceTester $I)
444     {
445         // I do something
446     }
447 }
448 ```
449
450 For Cept you should use simple comments:
451
452 ```php
453 <?php
454 // @env firefox
455 // @env phantom
456 ```
457
458 This way you can easily control which tests will be executed for each environment.
459
460 ### Current values
461
462 Sometimes you may need to change the test behavior in real time.
463 For instance, the behavior of the same test may differ in Firefox and in Chrome.
464 In runtime we can retrieve the current environment name, test name,
465 or list of enabled modules by calling the `$scenario->current()` method.
466
467 ```php
468 <?php
469 // retrieve current environment
470 $scenario->current('env');
471
472 // list of all enabled modules
473 $scenario->current('modules');
474
475 // test name
476 $scenario->current('name');
477
478 // browser name (if WebDriver module enabled)
479 $scenario->current('browser');
480
481 // capabilities (if WebDriver module enabled)
482 $scenario->current('capabilities');
483 ```
484
485 You can access `\Codeception\Scenario` in the Cept and Cest formats.
486 In Cept, the `$scenario` variable is available by default,
487 while in Cest you should retrieve it through dependency injection:
488
489 ```php
490 <?php
491 public function myTest(\AcceptanceTester $I, \Codeception\Scenario $scenario)
492 {
493     if ($scenario->current('browser') == 'phantomjs') {
494       // emulate popups for PhantomJS
495       $I->executeScript('window.alert = function(){return true;}');
496     }
497 }
498 ```
499
500 `Codeception\Scenario` is also available in Actor classes and StepObjects. You can access it with `$this->getScenario()`.
501
502 ### Dependencies
503
504 With the `@depends` annotation you can specify a test that should be passed before the current one.
505 If that test fails, the current test will be skipped. You should pass the method name of the test you are relying on.
506
507 ```php
508 <?php
509 class ModeratorCest {
510
511     public function login(AcceptanceTester $I)
512     {
513         // logs moderator in
514     }
515
516     /**
517      * @depends login
518      */
519     public function banUser(AcceptanceTester $I)
520     {
521         // bans user
522     }
523 }
524 ```
525
526 `@depends` applies to the `Cest` and `Codeception\Test\Unit` formats. Dependencies can be set across different classes.
527 To specify a dependent test from another file you should provide a *test signature*.
528 Normally, the test signature matches the `className:methodName` format.
529 But to get the exact test signature just run the test with the `--steps` option to see it:
530
531 ```
532 Signature: ModeratorCest:login`
533 ```
534
535 Codeception reorders tests so dependent tests will always be executed before the tests that rely on them.
536
537 ### Shuffle
538
539 By default Codeception runs tests in alphabetic order. 
540 To ensure that tests are not depending on each other (unless explicitly declared via `@depends`) you can enable `shuffle` option.
541
542 ```yaml
543 # inside codeception.yml
544 settings:
545     shuffle: true
546 ```
547
548 Alternatively, you may run tests in shuffle without changing the config:
549
550 ```
551 codecept run -o "settings: shuffle: true"
552 ```
553
554
555 Tests will be randomly reordered on each run. When tests executed in shuffle mode a seed value will be printed. 
556 Copy this seed value from output to be able to rerun tests in the same order.
557
558 ```
559 $ codecept run 
560 Codeception PHP Testing Framework v2.4.5
561 Powered by PHPUnit 5.7.27 by Sebastian Bergmann and contributors.
562 [Seed] 1872290562
563 ```
564
565 Pass the copied seed into `--seed` option:  
566
567 ```
568 codecept run --seed 1872290562
569 ```  
570
571
572
573 ## Interactive Console
574
575 The interactive console was added to try Codeception commands before executing them inside a test.
576
577 ![console](http://codeception.com/images/console.png)
578
579 You can run the console with the following command:
580
581 ``` bash
582 $ php vendor/bin/codecept console suitename
583 ```
584
585 Now you can execute all the commands of an appropriate Actor class and see the results immediately.
586 This is especially useful when used with the `WebDriver` module. It always takes too long to launch Selenium
587 and the browser for tests. But with the console you can try different selectors, and different commands,
588 and then write a test that should pass when executed.
589
590 And a special hint: show your boss how you can easily manipulate web pages with the console and Selenium.
591 It will be easy to convince them to automate this step and introduce acceptance testing to the project.
592
593 ## Running from different folders
594
595 If you have several projects with Codeception tests, you can use a single `codecept` file to run all of your tests.
596 You can pass the `-c` option to any Codeception command (except `bootstrap`), to execute Codeception in another directory:
597
598 ```bash
599 $ php vendor/bin/codecept run -c ~/projects/ecommerce/
600 $ php vendor/bin/codecept run -c ~/projects/drupal/
601 $ php vendor/bin/codecept generate:cept acceptance CreateArticle -c ~/projects/drupal/
602 ```
603
604 To create a project in directory different from the current one, just provide its path as a parameter:
605
606 ```bash
607 $ php vendor/bin/codecept bootstrap ~/projects/drupal/
608 ```
609
610 Also, the `-c` option allows you to specify another config file to be used.
611 Thus, you can have several `codeception.yml` files for your test suite (e.g. to to specify different environments
612 and settings). Just pass the `.yml` filename as the `-c` parameter to execute tests with specific config settings.
613
614 ## Groups
615
616 There are several ways to execute a bunch of tests. You can run tests from a specific directory:
617
618 ```bash
619 $ php vendor/bin/codecept run tests/acceptance/admin
620 ```
621
622 You can execute one (or several) specific groups of tests:
623
624 ```bash
625 $ php vendor/bin/codecept run -g admin -g editor
626 ```
627
628 The concept of groups was taken from PHPUnit and behave in the same way.
629
630 For Test and Cest files you can use the `@group` annotation to add a test to a group.
631
632 ```php
633 <?php
634 /**
635  * @group admin
636  */
637 public function testAdminUser()
638 {
639 }
640 ```
641
642 For Cept files, use pseudo-annotations in comments:
643
644 ```php
645 <?php
646 // @group admin
647 // @group editor
648 $I = new AcceptanceTester($scenario);
649 $I->wantToTest('admin area');
650 ```
651
652 For `.feature`-files (Gherkin) use tags:
653
654 ```gherkin
655 @admin @editor
656 Feature: Admin area
657 ```
658
659 ### Group Files
660
661 Groups can be defined in global or suite configuration files.
662 Tests for groups can be specified as an array of file names or directories containing them:
663
664 ```yaml
665 groups:
666   # add 2 tests to db group
667   db: [tests/unit/PersistTest.php, tests/unit/DataTest.php]
668
669   # add all tests from a directory to api group
670   api: [tests/functional/api]
671 ```
672
673 A list of tests for the group can be passed from a Group file. It should be defined in plain text with test names on separate lines:
674
675 ```bash
676 tests/unit/DbTest.php
677 tests/unit/UserTest.php:create
678 tests/unit/UserTest.php:update
679 ```
680 A group file can be included by its relative filename:
681
682 ```yaml
683 groups:
684   # requiring a group file
685   slow: tests/_data/slow.txt
686 ```
687
688 You can create group files manually or generate them from third party applications.
689 For example, you can write a script that updates the slow group by taking the slowest tests from xml report.
690
691 You can even specify patterns for loading multiple group files with a single definition:
692
693 ```yaml
694 groups:
695   p*: tests/_data/p*
696 ```
697
698 This will load all found `p*` files in `tests/_data` as groups. Group names will be as follows p1,p2,...,pN.
699
700 ## Formats
701
702 In addition to the standard test formats (Cept, Cest, Unit, Gherkin) you can implement your own format classes to customise your test execution.
703 Specify these in your suite configuration:
704
705 ```yaml
706 formats:
707   - \My\Namespace\MyFormat
708 ```
709
710 Then define a class which implements the LoaderInterface
711
712 ```php
713 namespace My\Namespace;
714
715 class MyFormat implements \Codeception\Test\Loader\LoaderInterface
716 {
717     protected $tests;
718     
719     protected $settings;
720     
721     public function __construct($settings = [])
722     {
723         //These are the suite settings
724         $this->settings = $settings;
725     }
726     
727     public function loadTests($filename)
728     {
729         //Load file and create tests
730     }
731
732     public function getTests()
733     {
734         return $this->tests;
735     }
736
737     public function getPattern()
738     {
739         return '~Myformat\.php$~';
740     }
741 }
742 ```
743
744 ## Shell auto-completion
745
746 For bash and zsh shells, you can use auto-completion for your Codeception projects by executing the following in your shell (or add it to your .bashrc/.zshrc):
747 ```bash
748 # BASH ~4.x, ZSH
749 source <([codecept location] _completion --generate-hook --program codecept --use-vendor-bin)
750
751 # BASH ~3.x, ZSH
752 [codecept location] _completion --generate-hook --program codecept --use-vendor-bin | source /dev/stdin
753
754 # BASH (any version)
755 eval $([codecept location] _completion --generate-hook --program codecept --use-vendor-bin)
756 ```
757
758 ### Explanation
759
760 By using the above code in your shell, Codeception will try to autocomplete the following:
761 * Commands
762 * Suites
763 * Test paths
764
765 Usage of `-use-vendor-bin` is optional. This option will work for most Codeception projects, where Codeception is located in your `vendor/bin` folder.
766 But in case you are using a global Codeception installation for example, you wouldn't use this option.
767
768 Note that with the `-use-vendor-bin` option, your commands will be completed using the Codeception binary located in your project's root.
769 Without the option, it will use whatever Codeception binary you originally used to generate the completion script ('codecept location' in the above examples)
770
771 ## Conclusion
772
773 Codeception is a framework which may look simple at first glance
774 but it allows you to build powerful tests with a single API, refactor them,
775 and write them faster using the interactive console. Codeception tests can be easily organized in groups or Cest classes.