A week ago
I already wrote about Codeception and its use for testing PHP applications. After the last post several bugs have been fixed. Thanks for the bug reports. If you have not tried Codeception, I advise you to look at the previous article and try it out for acceptance tests.
Today I want to tell you how BDD-style unit testing is implemented in Codeception.
I note that the module for testing units is still experimental. Not in the sense of "unstable", but in the sense of "may and will be expanded to meet all the necessary needs."
Before starting to talk about the BDD testing of units, I will answer the quite logical question that you naturally have: nafiga goat bayan? That is, why do we need any pribludy to unit tests, if they already work fine in the same PHPUnit. Why rewrite them in the scenario paradigm?
')
All tests should be readable. In particular, unit tests, where the test essentially describes the functionality of the method. But he is overloaded with the preparation of the environment, the creation of stubs and mocks. Many different calls are mixed in it and sometimes there are no comments at all. And for a new person who breaks a test, it is difficult to understand what is being tested and how.
Codeception offers an approach where each step describes the action to be performed.
For example, like this:
<?php class UserCest { function setNameAndSave(CodeGuy $I) { $I->wantToTest('getter and setter of User model'); $I->execute(function () { $user = new Model\User; $user->setName('davert'); $user->save(); }); $I->seeInDatabase('users',array('name' => 'davert'); } } ?>
And why is it necessary? This is how the executable code is separated from the checks. However, no one forbids the use of asserts inside the code block:
<?php $I->wantToTest('getter and setter of User model'); $I->execute(function () { $user = new Model\User; $user->setName('davert'); assertEquals('davert', $user->getName()); $user->save(); }); $I->seeInDatabase('users',array('name' => 'davert');
The more difficult the test becomes, the more it is required to maintain its readability.
Codeception becomes interesting when you need to test business logic classes. They usually do not exist in isolation, but depending on other classes, and the result of their implementation is sometimes not so easy to check as for the getter.
Take this simple controller from an imaginary MVC framework.
<?php class UserController extends AbtractController { public function show($id) { $user = $this->db->find('users',$id); if (!$user) return $this->render404('User not found'); $this->render('show.html.php', array('user' => $user)); return true; } } ?>
What he does, in principle, is understandable. Shows the user profile page. But testing it is difficult, because before testing, you need to isolate the controller from View and Model. Here is how we do it in Codeception.
<?php class UserControllerCest { public $class = 'UserController'; public function show(CodeGuy $I) { $I->haveStub($controller = Stub::makeEmptyExcept($this->class, 'show')) ->haveStub($db = Stub::make('DbConnector', array( 'find' => function($id) { return $id ? new User() : null ))) ->setProperty($controller, 'db', $db); $I->wantTo('render profile page for valid user') ->executeTestedMethodOn($controller, 1) ->seeResultEquals(true) ->seeMethodInvoked($controller, 'render'); $I->expect('it will render page 404 for unexistent user') ->executeTestedMethodOn($controller, 0) ->seeResultNotEquals(true) ->seeMethodInvoked($controller, 'render404','User not found') ->seeMethodNotInvoked($controller, 'render'); } }
As the same test I wrote in PHPUnit can be viewed
here . It turned out to be 1.5 times longer, and the code, of course, is understandable, but if you are a PHPUnit guru.
What is good in our code: it has a clear structure. First we create the environment, then we perform the actions and check the results. Please note, we check if the 'render' method was executed from the controller
after we performed our 'show' method with parameter 1. Thus, we do not mix the definition of stubs with asserts. All checks go after the execution of the tested code.
About readability. Let's try to creatively translate this code into the text:
With this method I can render profile page for valid user
If I execute this method
I will see result equals: true
I will see method invoked: $ controller, 'render'
I expect it will render page 404 for unexistent user
If I execute this method
I will see result not equals: true
I will see method invoked: $ controller, 'render404'
I will see method not invoked: $ controller, 'render'
Though take and write to the documentation. Maybe soon the documentation generation will be added, but for now I just want to demonstrate how clearly and clearly the test code itself describes the code under test.
Notice how stubs are created. Any stub is done by one team. For example:
<?php
This is simpler than what PHPUnit offers. Think of at least how many parameters mockBuilder requires and what they all mean. But what is most interesting, the Stub class is just a wrapper over mockBuilder. Notice, we create only stubs, i.e. Wednesday And of them, dynamically, with the same seeMethodInvoked command, we convert the stub into mock.
More information in the
documentation and in the
Unit module
As I said at the beginning, this thing is experimental, which means it can be discussed. But it was written not for “spherical code in a vacuum”, but on the basis of its own real needs. However, I advise you to try for your project. If some moments are poorly covered in the documentation - ask.
PS An
article about Codeception and Zend Framework integration has appeared on the site
.