Skip to content

Commit 5949fa6

Browse files
author
davert
committed
=auto-updated documentation
1 parent 1324e17 commit 5949fa6

File tree

3 files changed

+150
-131
lines changed

3 files changed

+150
-131
lines changed

_includes/guides.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
<li><a href="/docs/01-Introduction">Introduction</a></li><li><a href="/docs/02-GettingStarted">Getting Started</a></li><li><a href="/docs/03-ModulesAndHelpers">Modules And Helpers</a></li><li><a href="/docs/04-AcceptanceTests">Acceptance Tests</a></li><li><a href="/docs/05-FunctionalTests">Functional Tests</a></li><li><a href="/docs/06-UnitTests">Unit Tests</a></li><li><a href="/docs/07-AdvancedUsage">Advanced Usage</a></li><li><a href="/docs/07-UnitTestsInScenarios">Unit Tests In Scenarios</a></li><li><a href="/docs/08-CestFormat">Cest Format</a></li><li><a href="/docs/09-Data">Data</a></li><li><a href="/docs/10-WebServices">Web Services</a></li><li><a href="/docs/11-Codecoverage">Codecoverage</a></li>
1+
<li><a href="/docs/01-Introduction">Introduction</a></li><li><a href="/docs/02-GettingStarted">Getting Started</a></li><li><a href="/docs/03-ModulesAndHelpers">Modules And Helpers</a></li><li><a href="/docs/04-AcceptanceTests">Acceptance Tests</a></li><li><a href="/docs/05-FunctionalTests">Functional Tests</a></li><li><a href="/docs/06-UnitTests">Unit Tests</a></li><li><a href="/docs/07-AdvancedUsage">Advanced Usage</a></li><li><a href="/docs/08-CestFormat">Cest Format</a></li><li><a href="/docs/09-Data">Data</a></li><li><a href="/docs/10-WebServices">Web Services</a></li><li><a href="/docs/11-Codecoverage">Codecoverage</a></li>

docs/07-AdvancedUsage.markdown

Lines changed: 148 additions & 129 deletions
Original file line numberDiff line numberDiff line change
@@ -3,216 +3,235 @@ layout: doc
33
title: Codeception - Documentation
44
---
55

6-
# Unit Tests Inside Scenario
6+
# Advanced Usage
77

8-
<div class="alert alert-error">This chapter is <strong>deprecated</strong>. Don't read it and forget everything I might have read.
9-
Use classical <a href="http://codeception.com/docs/06-UnitTests">Unit Tests</a> with some Codeception powers. <a href="http://codeception.com/03-18-2013/scenario-unit-deprecated.html">Here is why it it happened</a></div>
8+
In this chapter we will cover some technics and options that you can use to improve your testing experience and stay with better organization of your project.
109

10+
## Interactive Console
1111

12-
Each function is like a little a little application in itself. It's the simplest and least divisible part of the program. It's natural to test it the way we test the application as a whole. Codeception unit tests are very similar to acceptance tests, with some additional features that simplify code testing.
12+
Interactive console was added to try Codeception commands before executing them inside a test.
13+
This feature was introduced in 1.6.0 version.
1314

14-
Unit tests are required to be as readable as possible. They should be clean and easy to understand. Codeception helps the developer to follow these practices.
15+
![console](http://img267.imageshack.us/img267/204/003nk.png)
1516

16-
## Writing a Simple Test
17-
18-
With Codeception you should describe your test in a scenario, as we did that for acceptance tests.
17+
You can execute console with
1918

2019
{% highlight php %}
20+
codecept.phar console suitename
2121

22-
<?php
22+
{% endhighlight %}
2323

24-
function validateEmail(CodeGuy $I)
25-
{
26-
$I->execute(function () {
27-
return Validator::validateEmail('davert@mail.ua');
28-
});
29-
$I->seeResultEquals(true);
30-
}
31-
?>
24+
Now you can execute all commands of appropriate Guy class and see immidiate results. That is especially useful for when used with Selenium modules. It always takes too long to launch Selenium and browser for tests. But with console you can try different selectors, and different commands, and then write a test that would pass for sure when executed.
3225

33-
{% endhighlight %}
26+
And a special hint: show your boss how you can nicely manipulate web pages with console and Selenium. With this you can convince him that it is easy to automate this steps and introduce acceptance testing to the project.
3427

35-
The similar test in PHPUnit would look like this:
28+
## Running from different folders.
29+
30+
If you have several project with Codeception tests in it you can use one `codecept.phar` file to run their tests.
31+
You can pass a `-c` option to any Codeception command, excluding `bootstrap` to execute codeception in other directory.
3632

3733
{% highlight php %}
34+
codecept.phar run -c ~/projects/ecommerce/
35+
php codecept.phar run -c ~/projects/drupal/
36+
php codecept.phar generate:cept acceptance CreateArticle -c ~/projects/drupal/
3837

39-
<?php
40-
public function testValidateEmail()
41-
$this->assertTrue(Validator::validateEmail('davert@mail.ua'));
42-
}
43-
?>
38+
{% endhighlight %}
39+
40+
To create a project in directory other then current just provide it's path as a parameter.
41+
42+
{% highlight php %}
43+
codecept.phar bootstrap ~/projects/drupal/
4444

4545
{% endhighlight %}
4646

47-
Well, PHPUnit wins here: its test is shorter and readable. **There is no practical reason for using Codeception for testing simple methods**. But not all functions can be executed and tested that way. Whenever a function has dependencies and it's results can't be so easily observable, Codeception will be quite useful.
47+
Basically `-c` option allows you specify not only the path but a config file to be used. Thus, you can have several `codeception.yml` file for your test suite. You may use it to specify different environments and settings. Just pass a filename into `-c` parameter to execute tests with specific config settings.
48+
49+
## Groups
50+
51+
There are several ways to execute bunch of tests. You can run tests from specific directory:
52+
53+
{% highlight php %}
54+
codecept.phar run tests/acceptance/admin
4855

49-
Using Codeception for unit testing is like using a framework for web development. Even if it's hard to create a 'hello world' page with Symfony, Zend, or Yii, they help you build complex web applications.
56+
{% endhighlight %}
5057

51-
## Testing the Controller
58+
Or execute one (or several) specific groups of tests:
5259

53-
It's natural to assume that you are using PHP for web development.
54-
Generally, web applications use the MVC (Model-View-Controller) pattern.
55-
Let's show how Codeception simplifies unit testing for controller classes.
60+
{% highlight php %}
61+
codecept.phar run -g admin -g editor
62+
63+
{% endhighlight %}
5664

57-
We have a controller class of an imaginary MVC framework:
65+
In this case all tests that belongs to groups admin or editor will be executed. Groups concept were taken from PHPUnit and in classical PHPUnit tests they behave just in the same way. To add Cept to the group - use `$scenario` variable:
5866

5967
{% highlight php %}
6068

6169
<?php
62-
class UserController extends AbstractController {
63-
64-
public function show($id)
65-
{
66-
$user = $this->db->find('users',$id);
67-
if (!$user) return $this->render404('User not found');
68-
$this->render('show.html.php', array('user' => $user));
69-
return true;
70-
}
71-
}
70+
$scenario->group('admin');
71+
$scenario->group('editor');
72+
// or
73+
$scenario->group(array('admin', 'editor'))
74+
// or
75+
$scenario->groups(array('admin', 'editor'))
76+
77+
$I = new WebGuy($scenario);
78+
$I->wantToTest('admin area');
7279
?>
7380

7481
{% endhighlight %}
82+
For Tests and Cests you can use annotation `@group` to add a test to the group.
7583

76-
We want to test this `show` method. As you can see it's rather different then the `validateEmail` function from the previous example.
77-
This is because the `show` method relies on other functions and classes.
84+
{% highlight php %}
7885

79-
When we are performing unit testing we should ignore all those dependencies in the test. We don't worry about how the render method works, or how the `$this->db` searches for a user instance.
80-
The only thing we test is the behavior of the `show` action.
81-
To test such a method, we should replace classes with their [stubs, and use mocks](http://martinfowler.com/articles/mocksArentStubs.html#TheDifferenceBetweenMocksAndStubs) for assertions.
86+
<?php
87+
/**
88+
* @group admin
89+
*/
90+
public function testAdminUser()
91+
{
92+
$this->assertEquals('admin', User::find(1)->role);
93+
}
94+
?>
8295

83-
### Codeception Test
96+
{% endhighlight %}
97+
Same annotation can be used in Cest classes.
98+
99+
## Cest Classes
84100

85-
For unit tests Codeception provides a different test file format. It's called Cest (Test + Cept = Cest).
101+
In case you want to get a class-like structure for your Cepts, instead of plain PHP, you can use Cest format.
102+
It is very simple and is fully compatible with Cept scenarios. It means If you feel like your test is long enough and you want to split it - you can easily move it into class.
86103

87-
Here is the Codeception test for the 'show' action:
104+
You can start Cest file by running the command:
88105

89106
{% highlight php %}
107+
codecept.phar generate:cest suitename CestName
90108

91-
<?php
92-
use Codeception\Util\Stub as Stub;
109+
{% endhighlight %}
93110

94-
const VALID_USER_ID = 1;
95-
const INVALID_USER_ID = 0;
111+
The generated file will look like:
96112

97-
class UserControllerCest {
98-
public $class = 'UserController';
113+
{% highlight php %}
99114

115+
<?php
116+
class BasicCest
117+
{
100118
101-
public function show(CodeGuy $I) {
102-
// prepare environment
103-
$I->haveFakeClass($controller = Stub::makeEmptyExcept($this->class, 'show'));
104-
$I->haveFakeClass($db = Stub::make('DbConnector', array('find' => function($id) { return $id == VALID_USER_ID ? new User() : null )));
105-
$I->setProperty($controller, 'db', $db);
119+
public function _before()
120+
{
121+
}
106122
107-
$I->executeTestedMethodOn($controller, VALID_USER_ID)
108-
->seeResultEquals(true)
109-
->seeMethodInvoked($controller, 'render');
123+
public function _after()
124+
{
125+
}
110126
111-
$I->expect('it will render 404 page for non existent user')
112-
->executeTestedMethodOn($controller, INVALID_USER_ID)
113-
->seeResultNotEquals(true)
114-
->seeMethodInvoked($controller, 'render404','User not found')
115-
->seeMethodNotInvoked($controller, 'render');
127+
// tests
128+
public function tryToTest(\WebGuy $I) {
129+
116130
}
117131
}
118132
?>
119133

120134
{% endhighlight %}
121135

122-
This test is written as a simple scenario. Every command of the scenario clearly describes the action being taken. Let's review this code.
123-
124-
First of all, take a look at the `Cest` suffix. By it, Codeception knows it's a Cest testing class. The public property `$class` is just as important. It defines the class which is being tested. Each public method of `$class` will be treated as a test. Please note, that the name of each test method is the same as the method which is actually being tested. In other words, to test `UserController->show` we use `UserControllerCest->show`, `UserController->edit => UserControllerCest->edit`, etc. The only parameter of the test method is the CodeGuy class instance.
125-
126-
With the CodeGuy we write a scenario for unit testing. The action `haveFakeClass` declares that we will use a stub in our testing. By using this command Codeception will dynamically create a mock for this class.
127-
Codeception uses a wrapper over PHPUnit's mocking library. It can create various stubs in a simple way. Later we will review this tool more deeply.
136+
**Each public method of Cest (except, those starting from `_`) will be executed as a test** and will receive Guy class as the first parameter and `$scenario` variable as a second.
128137

129-
For `UserController` we redefine all of it's methods, except the tested one, with dummies.
130-
For the `$db` property, which is supposed to be a DbConnector (Database class) instance we redefine its `find` method. Depending on a parameter it is supposed to return a User model or `null`.
138+
In `_before` and `_after` method you can use common setups, teardowns for the tests in the class. That actually makes Cest tests more flexible, then Cepts that rely only on similar methdods in Helper classes.
131139

132-
Next we connect both stubbed classes. We'd normally use:
140+
As you see we are passing Guy class into `tryToTest` stub. That allows us to write a scenarios the way we did before.
133141

134142
{% highlight php %}
135143

136144
<?php
137-
$controller->db = $db;
145+
class BasicCest
146+
{
147+
// test
148+
public function checkLogin(\WebGuy $I) {
149+
$I->wantTo('log in to site');
150+
$I->amOnPage('/');
151+
$I->click('Login');
152+
$I->fillField('username', 'jon');
153+
$I->fillField('password','coltrane');
154+
$I->click('Enter');
155+
$I->see('Hello, Jon');
156+
$I->seeInCurrentUrl('/account');
157+
}
158+
}
138159
?>
139160

140161
{% endhighlight %}
141162

142-
But the `$controller->db` property might be protected. By using the`setProperty` command we can even set the values of protected and private properties! This can be done with the power of [Reflection](http://php.net/manual/en/book.reflection.php).
163+
But there is a limiation in Cest files. It can't work with `_bootstrap.php` the way we did in scenario tests.
164+
It was useful to store some variables in bootstraps that should be passed into scenario.
165+
In Cest files you should inject all external variables manually, using static or global variables.
143166

144-
The environment has been prepared. There are no dependencies left. Now we can concentrate on the actual testing.
167+
As a workaround you can choose [Fixtures](https://github.com/Codeception/Codeception/blob/master/src/Codeception/Util/Fixtures.php) class which is nothing more then global storage to your variables. You can pass variables from `_bootstrap.php` or any other place just with `Fixtures::add()` call. But probably you can use Cest classes `_before` and `_after` methods to load fixtures on the start of test, and deleting them afterwards. Pretty useful too.
145168

146-
First test case -- we execute the `show` action for an existing user.
147-
We see that the tested method returns true and the method `render` was invoked.
169+
As you see, Cest class have no parent like `\Codeception\TestCase\Test` or `PHPUnit_Framework_TestCase`. That was done intentionally. This allows you to extend class any time you wnat by attaching any meta-testing class to it's parent. In meta class you can write common behaviors and workarounds that may be used in child class. But don't forget to make them `protected` so they won't be executed as a tests themselves.
148170

149-
To ensure 100% code coverage of this method we should test the negative scenario too, avoiding the [Happy Path anti-pattern](http://www.ibm.com/developerworks/opensource/library/os-junit/).
171+
Also you can define `_failed` method in Cest class which will be called if test finished with `error` or fail.
150172

151-
Our second test case is running the `show` action for a non-existent user. The 404 page should be rendered in this case.
173+
## Refactoring
152174

153-
The `expect` command we use here is as good as a comment. We describe the expected result if it's not obvious. Actually we can guess that the `UserController->show` method would show a user. But we can't be sure what would happen if the user doesn't exist.
154-
That's why we use 'expect' to describe the function description.
175+
As test base growth they will require refactoring, sharing common variables and behaviors. The classical example for this is `login` action which will be called for maybe every test of your test suite. It's wise to make it written one time and use it in all tests.
155176

156-
### PHPUnit Example
157-
158-
To prove Codeception was useful for testing the controller, we will write the same test in PHPUnit.
159-
Remember, it can be run with Codeception too.
177+
It's pretty obvious that for such cases you can use your own PHP classes to define such methods.
160178

161179
{% highlight php %}
162180

163-
<?php
164-
165-
class UserControllerTest extends PHPUnit_Framework_TestCase
181+
<?php class TestCommons
166182
{
167-
protected function prepareController()
168-
{
169-
$controller = $this->getMock('UserController', array('render', 'render404'), null, false, false);
170-
$db = $this->getMock('DbConnector');
171-
$db->expects($this->any())
172-
->method('find')
173-
->will($this->returnCallback(function ($id) { return $id ? new User() : null; }));
174-
175-
// connecting stubs together
176-
$r = new ReflectionObject($controller);
177-
$dbProperty = $r->getProperty('db');
178-
$dbProperty->setAccessible(true);
179-
$dbProperty->setValue($controller, $db);
180-
}
181-
182-
public function testShowForExistingUser()
183-
{
184-
$controller = $this->prepareController();
185-
$controller->expects($this->once())->method('render')->with($this->anything());
186-
$this->assertTrue($controller->show(1));
187-
}
183+
public static $username = 'jon';
184+
public static $password = 'coltrane';
188185
189-
public function testShowForUnexistingUser()
186+
public static logMeIn($I)
190187
{
191-
$controller = $this->prepareController();
192-
$controller->expects($this->never())->method('render')->with($this->anything());
193-
$controller->expects($this->once())->method('404')->with($this->equalTo('User not found'));
194-
$this->assertNotEquals(true, $controller->show(0));
188+
$I->amOnPage('/login');
189+
$I->fillField('username', 'jon');
190+
$I->fillField('password','coltrane');
191+
$I->click('Enter');
195192
}
196193
}
197194
?>
198195

199196
{% endhighlight %}
200-
This test is 1.5 times longer. One test is split into two. Mocking requires strong knowledge of the PHPUnit API. It's hard to understand the behavior of the tested method `show` without looking into its code.
201-
Nevertheless this test is quite readable.
202197

203-
Let's analyze why we split the one test into two.
204-
We are testing one feature, but two different behaviors. This can be argued, but we suppose that showing a "404 page" can't be treated as a feature of your product.
198+
This file can be required in `_bootstrap.php` file
205199

206-
Having two tests instead of one is a technical, not a logical, limitation of PHPUnit.
207-
That's because you can't test the method `render` invoked once and not invoked in a single test. All mocked methods invocations are checked at the very end of the test. Thus, having expectations for both 'render once' and 'render never', we will only see that the last one has been performed.
200+
{% highlight yaml %}
208201

209-
## Conclusion
202+
<?php
203+
// bootstrap
204+
require_once '/path/to/test/commons/TestCommons.php';
205+
?>
206+
207+
{% endhighlight %}
208+
209+
and used in your scenarios:
210+
211+
{% highlight yaml %}
210212

211-
You are free to decide the testing framework you will use for unit tests. Codeception and PHPUnit run the same engine.
212-
Codeception provides you with a cool DSL to simplify your unit tests. You are writing the scenario definitions, not the actual executed code. Behind the scenes all the dirty work is done by Codeception. You write only the testing logic.
213+
<?php
214+
$I = new WebGuy($scenario);
215+
TestCommons::logMeIn($I);
216+
?>
217+
218+
{% endhighlight %}
219+
220+
You should get the idea by now. Codeception doesn't provide any particular strategy for you to manage the tests. But it is flexible enough to create all the support classes you need for your test suites. The same way, with custom classes you can implement `PageObject` and `StepObject` patterns.
221+
222+
## PageObject and StepObjects
223+
224+
In next versions Codeception will provide PageObjects and StepObjects out of the box.
225+
But today we encourage you to try your own implementations. Whe have some nice blogpost for you to learn what are PageObjects, why you ever want to use them and how they can be implemented.
226+
227+
* [Ruling the Swarm (of Tests)](http://phpmaster.com/ruling-the-swarm-of-tests-with-codeception/) by Michael Bodnarchuk.
228+
* [Implementing Page Objects in Codeception](http://jonstuff.blogspot.ca/2013/05/implementing-page-objects-in.html) by Jon Phipps.
229+
230+
## Conclusion
213231

232+
Codeception is a framework which may look simple at first sight. But it allows you to build powerful test with one APIs, refactor them, and write them faster using interactive console. Codeception tests can easily be organized with groups or cest classes. Probably too much abilities for the one framework. But nevertheless Codeception follows the KISS pricinple: it's easy to start, easy to learn, easy to extend.
214233

215234

216235

217-
* **Next Chapter: [UnitTestsInScenarios >](/docs/07-UnitTestsInScenarios)**
236+
* **Next Chapter: [CestFormat >](/docs/08-CestFormat)**
218237
* **Previous Chapter: [< UnitTests](/docs/06-UnitTests)**

docs/08-CestFormat.markdown

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -365,4 +365,4 @@ Codeception has it's powers and it's limits. We believe Codeception's limitation
365365

366366

367367
* **Next Chapter: [Data >](/docs/09-Data)**
368-
* **Previous Chapter: [< UnitTestsInScenarios](/docs/07-UnitTestsInScenarios)**
368+
* **Previous Chapter: [< AdvancedUsage](/docs/07-AdvancedUsage)**

0 commit comments

Comments
 (0)