SlideShare uma empresa Scribd logo
1 de 43
Baixar para ler offline
TRAINING COURSE PHPUNIT
Training Course PHPUnit
Training Course PHPUnit Nick Belhomme 2010 p. 1
TRAINING COURSE PHPUNIT
Goal of this course
We will introduce you into the art of unit testing.
What is Unit testing and how does it work. Together we will build a testing suite for a domain
model (computer text based game) and finally implement this model into Zend Framework 1.11 and
up. Making you comfortable with the PHPUnit testing framework.
Training Course PHPUnit Nick Belhomme 2010 p. 2
TRAINING COURSE PHPUNIT
What where how?
Training Course PHPUnit Nick Belhomme 2010 p. 3
TRAINING COURSE PHPUNIT
What is Unit Testing?
Unit Testing is the testing of units. The tests are done to ensure that each unit is working like it is
intended to work.
The tests should be automatic and performed on regular intervals performing validation and
verification on the correct working of the units tested. Ideally each test should be independent of
another. A unit to be tested is a small piece of software code. Today in Object oriented programming
languages these small pieces of software code are generally individual methods from a specific
class. Unit testing gives us a way to test our implementations, design and behavior on the classes we
write.
Today we can claim that Unit testing is a fundamental part of quality modern software development.
Benefits of Unit Testing
Unit testing has three main benefits:
• It makes you think about your application and about the implementation route you have
chosen / are about to chose instead of running off half cocked implementing bad design
decisions.
• It gives an immediate view on the proper functioning of the system and facilitates locating a
defect in the system.
• It facilitates the refactoring of existing code. Which is equally important to guarantee the
longevity of an application.
"Refactoring is the process of changing a software system in such a way that it does not
alter the external behavior of the code yet improves its internal structure. It is a disciplined
way to clean up code that minimizes the chances of introducing bugs. [Preface Refactoring
Martin Fowler]"
When something breaks the application because of external factors (ie. DB credential changes) or
because changes made to the code, Unit Tests will quickly show you were the error or bug has been
introduced. Allowing you to quickly locate and fix the bug. In the end speeding up development
time.
Because of the tests in place you as a developer will feel more confident in your code and in your
future changes. Reflecting that confidence to customers, management and other developers.
Training Course PHPUnit Nick Belhomme 2010 p. 4
TRAINING COURSE PHPUNIT
Testing pieces of software
Mostly for (PHP) developers testing their code was programming something and displaying it in the
browser. If it worked we moved on to the next piece of software to implement.
In a modern development cycle unit testing is a core component. Instead of testing stuff in the
browser we also test by automating different scenarios.
“In general if you think you need a var_dump() for testing your code, you are better of making that
test eternally embedded in your test-suite. Martin-Fowler”
Every programmer makes mistakes. What differentiates the good from the bad is that the good
programmer uses tests to detect his mistakes ASAP. Reducing debugging costs to a minimum and
making the product more stable.
Okay up to our first test. We are going to test the CAST operator
<?php
$fixture = new stdClass();
$fixture->id = 1248;
$fixture->name = 'Robin Hood';
// $fixture is expected to be an object from the type stdClass
// with two properties set
$fixture = (array) $fixture;
// $fixture is expected to be an array
// with two key value pairs set
If we want to check if the behavior of the (array) cast operator is working as intended we can add a
simple check
<?php
$fixture = new stdClass();
$fixture->id = 1248;
$fixture->name = 'Robin Hood';
echo is_object($fixture), PHP_EOL;
echo $fixture->id == 1248, PHP_EOL;
echo $fixture->name == 'Robin Hood', PHP_EOL;
$fixture = (array) $fixture;
echo is_array($fixture),PHP_EOL;
echo $fixture['id'] == 1248, PHP_EOL;
echo $fixture['name']== 'Robin Hood', PHP_EOL;
Training Course PHPUnit Nick Belhomme 2010 p. 5
TRAINING COURSE PHPUNIT
Everything is working as expected because we get for the 6 tests all 1
But at this time the tests are not automated and still require a human to check if every test has
returned 1.
Scanning over a big list of “ones” could get tiring and frustrating if you have to do it several times a
day.
We should automate this so that the tests only report unwanted behavior.
Let's improve this.
<?php
$fixture = new stdClass();
$fixture->id = 1248;
$fixture->name = 'Robin Hood';
assertTrue(is_object($fixture));
assertTrue($fixture->id == 1248);
assertTrue($fixture->name == 'Robin Hood');
$fixture = (array) $fixture;
assertTrue(is_array($fixture));
assertTrue($fixture['id'] == 1248);
assertTrue($fixture['name']== 'Robin Hood');
function assertTrue($condition)
{
if (!$condition) {
throw new Exception('Assertion failed.');
}
}
The tests are now fully automated and only report when something fails.
There are several test suites readily available to make life easier for you as a developer. These test
suites have the functions like assertTrue already implemented amongst many others.
This course is on the topic on the XUnit family of testing frameworks and more specifically
PHPUnit.
Training Course PHPUnit Nick Belhomme 2010 p. 6
TRAINING COURSE PHPUNIT
PHPUnit as a part of the XUnit Family
The XUnit family is a collective of various code-driven testing frameworks. They provide an
automated solution with no need to write the same tests many times, and no need to
remember what the result should be of each test. The name XUnit is a derivation of JUnit the first to
be widely known.
Each programming language has his own XUnit framework(s).
The two most famous Unit testing frameworks for PHP are SimpleTest by Marcus Baker and
PHPUnit by Sebastian Bergmann.
In this course we have chosen to elaborate PHPUnit. The reason is because it is more widely
adopted for PHP. Which is reflected by the adoption for it in major PHP frameworks like Zend
Framework and Symfony. Another reason is SimpleTest is not really maintained anymore.
When to create Unit tests
When you have classes which aren't covered yet by tests you should start to setup a testing suite to
make sure your code gets validated and checked on a regular interval. If you put those tests in place
you will become more confident in adding extra functionality and refactoring your existing code
base.
You could follow this methodology. Writing first the classes and then the tests. While this is better
than writing no tests at all it has some drawbacks:
• Some tests will not get written.
• Not that much thought will be put in the initial implementation. And putting initial thought
into your implementation is a key principal for every good programmer.
If we analyse the previous thought cycle we come to the conclusion that it is a wise decision to put
first the tests in place and then the implementation. This is exactly what Test Driven Development
TDD is all about. It dictates that all tests should be written prior to implementing the code.
TDD is a derivation from Xtreme Programming and it makes much sense to develop this way.
When we develop an application we should think about it before implementing it. TDD forces this
path. And puts tests in place before even implementing a single piece of application code.
But you shouldn't think in terms of tests but in terms of behavior. This brings us to Behavior Driven
Development. Which has the benefits of TDD but shifts the focus.
“So if it's not about testing, what's it about?
It's about figuring out what you are trying to do before you run off half-cocked to try to do it. You
write a specification that nails down a small aspect of behaviour in a concise, unambiguous, and
executable form. It's that simple. Does that mean you write tests? No. It means you write
specifications of what your code will have to do. It means you specify the behaviour of your code
ahead of time. But not far ahead of time. In fact, just before you write the code is best because that's
when you have as much information at hand as you will up to that point. Like well done TDD,
Training Course PHPUnit Nick Belhomme 2010 p. 7
TRAINING COURSE PHPUNIT
you work in tiny increments... specifying one small aspect of behaviour at a time, then implementing
it. When you realize that it's all about specifying behaviour and not writing tests, your point of view
shifts. Suddenly the idea of having a Test class for each of your production classes is ridiculously
limiting. And the thought of testing each of your methods with its own test method (in a 1-1
relationship) will be laughable. —Dave Astels
.
Training Course PHPUnit Nick Belhomme 2010 p. 8
TRAINING COURSE PHPUNIT
Getting Started
Training Course PHPUnit Nick Belhomme 2010 p. 9
TRAINING COURSE PHPUNIT
Installing PHPUnit
Installing PHPUnit is straight forward. You use the Pear Installer, set up your path (which should
already be done for PEAR) and that's it.
1. If you have PEAR already installed skip to step 2.
go to your phpPEAR directory and do:
WINDOWS:
php phar.require_hash=0 go-pear.phar
register the PEAR path to your system environment:
right click "this computer" => "properties" => "advanced" => "environment variables".
In the field "system variables" select PATH, "edit" and add the full path to PEAR to
the end of the list.
*NIX:
php go-pear.phar
register the PEAR path to your system environment
2. update your local PEAR environment:
pear channel-discover pear.phpunit.de
3. install PHPUnit
pear install phpunit/PHPUnit
Defining the project
We are going to build a text based adventure game. The setup of the game will be much alike the
old adventure games of Sierra and Lucas Arts but then purely text based.
We will have a character (you) which can travel from location to location, picking up objects,
manipulating the objects and talking to characters.
So we will need:
Grid : the gaming world holding all of the locations. We should be able to change position on the
grid.
Training Course PHPUnit Nick Belhomme 2010 p. 10
TRAINING COURSE PHPUNIT
Tile: a location on the grid. This can be a room or for instance a jungle. We should be able to
perform various actions on it and store objects in it.
Item: an item on which various actions can be added in order to manipulate it. Also possibility to
talk to the item.
ItemCombination: Defines which two items can be used together.
Action: an action, manipulation.
Inventory: a list of items.
Conversation: A conversation which can be linked to an item.
All this classes will have their own prefix Game_ which will serve as a name space.
It is always good practice to define each project and library with a specific name space / prefix. This
way same name clashes will most likely not occur.
Training Course PHPUnit Nick Belhomme 2010 p. 11
TRAINING COURSE PHPUNIT
Writing your first test case: Implementing the Grid
First requirement is we will have to create a class called Game_Grid. Let's define this in a unit test.
Tests/PHPUnit/Library/Game/GridTest.php
<?php
require_once 'PHPUnit/Framework.php';
require_once '../../../../Library/Game/Grid.php';
class Game_GridTest extends PHPUnit_Framework_TestCase
{
public function testConstructor()
{
$grid = new Game_Grid();
}
}
running the test will result a FATAL ERROR!
Training Course PHPUnit Nick Belhomme 2010 p. 12
TRAINING COURSE PHPUNIT
This is because we didn't create the Game/Grid.php file yet OR we haven't setup our include path
correctly OR the file Grid.php doesn't include a class of the type Game_Grid. Which can't be auto
loaded.
Once we have fixed these possible reasons we do not receive the fatal error anymore.
Instead we get OK (1 test, 0 assertions)
Let us analyze the unit test code.
First we include our PHPUnit framework and our class to test with require statements.
All Unit test cases are in the [class name]Test format. You take the class name to test and append it
with Test.
The class extends PHPUnit_Framework_TestCase which will give you access to the full power of
Training Course PHPUnit Nick Belhomme 2010 p. 13
TRAINING COURSE PHPUNIT
PHPUnit. This includes various template methods such as setUp() and tearDown(), assertion methods,
mocking, etc.
We let PHPUnit find every test by sticking to the naming format that every function starting with
the string test, is a test.
So every test method starts with test appended with a string of choice.
It is best to give a descriptive method name explaining what you are testing.
In example: public function testGetDescriptionToReturnFalseOnInitialisation()
PHPUnit finds all the tests by reflection. It searches for all methods of the format test* and will
execute them in the order defined. You should make sure every test is independent of another. And
finally all tests can have multiple assertions. In our test we didn't include any assertions yet but they
will grow rapidly.
So what did we do to fix the bug?
1) We first create our application structure
It is good practice to separate the tests and the actual library into two distinct folders.
We create a sub folder in Tests because several testing suites can be available for an
application, PHPUnit is one of them. The test case classes in the Tests/PHPUnit directory
(will) mirror the package and class structure of the System Under Test (SUT).
Probably the easiest way to compose a test suite is to keep all test case source files in a test
directory. PHPUnit can automatically discover and run the tests by recursively traversing the
Training Course PHPUnit Nick Belhomme 2010 p. 14
TRAINING COURSE PHPUNIT
test directory.
2) Then we start by creating our unit test
We created our first test called GridTest.php with the above Unit test embedded.
All test cases respect the following file naming convention:
[class-name-to-test] +“Test.php”
3) We execute the command: "phpunit GridTest.php" in the folder where the test is located.
This will result in a fatal error: require_once...
4) We add the file Grid.php in the Library directory.
5) Re-run the test again and we will see it fail once more: Class not found
Training Course PHPUnit Nick Belhomme 2010 p. 15
If you get an error that your operating system doesn't recognize phpunit, you
haven't set your execution rights or the path environment variables correct.
After installing PHPUnit you should be able to execute the phpunit command
from everywhere in the file system.
TRAINING COURSE PHPUNIT
6) Add the class definition into the Grid.php file.
Library/Game/Grid.php
<?php
class Game_Grid {}
7) run the test once more and see it pass.
Bootstrapping PHPUnit test cases
Because a lot of basic functions are repetitive and should be loaded for each test we could bootstrap
it. In the bootstrap we can for instance set the include path, or an auto loader. Or any other directive
that your application needs for running correctly. You can load a bootstrap file by passing it as a
parameter
PHPUnit --bootstrap [bootstrap file] [test case to run]
in example: PHPUnit --bootstrap Bootstrap.php GridTest.php
In our test suite we will be bootstrapping our tests. This will include a basic auto loader so we do
not have to write all the includes all the time. When we run our application itself we will also auto
load it. Everything will be auto loaded in the application. So why should our unit tests be any
different.
Create a file called Bootstrap.php (you can call it whatever you want but it is always a good idea to
stick to naming conventions. Because PHPUnit regards it as a bootstrap, you might as well name it
Training Course PHPUnit Nick Belhomme 2010 p. 16
TRAINING COURSE PHPUNIT
that way).
In the bootstrap we make sure our PHPUnit framework is loaded and implement a simple auto
loader.
<?php
require_once 'PHPUnit/Framework.php';
function __autoload($className)
{
$path = explode('_', $className);
$root = '';
if ($path[0] == 'App') {
$root = '../../../../Application/Library/';
array_shift($path);
} else if ($path[0] == 'Game') {
$root = '../../../../Library/Game/';
array_shift($path);
}
require_once $root.implode(DIRECTORY_SEPARATOR, $path).'.php';
}
Now that we have the auto loader in place we can remove the require_once from our tests.
Training Course PHPUnit Nick Belhomme 2010 p. 17
TRAINING COURSE PHPUNIT
Configure PHPUnit with a phpunit.xml configuration file
You can pass a lot of optional parameters to PHPUnit. For a complete list run the command without
any test to run, any parameter or with the parameter --help
PHPUnit
PHPUnit --help
We can use a configuration file to automagically pass parameters to the phpunit command.
You can define which bootstrap to load and where to find it. Which tests should be run etc.
PHPUnit will by default search for a file called phpunit.xml in the directory you run the command
from. Of course you can name it any other file name. But we suggest you sticking to that. If you
need multiple configuration files you can name them whatever you want.
In example:
phpunit.xml for normal testing
phpunitCruisecontrol.xml for a special cruisecontrol setup which is a part of the continuous
integration methodology.
Let's take a closer look at the configuration file we will setup:
Training Course PHPUnit Nick Belhomme 2010 p. 18
TRAINING COURSE PHPUNIT
Tests/PHPUnit/Library/Game/phpunit.xml
<?xml version="1.0" encoding="utf-8"?>
<phpunit bootstrap="./Bootstrap.php">
<testsuite name="Game">
<directory>./</directory>
</testsuite>
<logging>
<log type="coverage-html" target="/tmp/PHPUnit/Coverage/"
charset="UTF-8" yui="true", highlight="false" lowUpperBound="35"
highLowerBound="70" />
</logging>
</phpunit>
This will tell PHPUnit you want the Bootstrap.php loaded as a bootstrap and the entire test suite
called Game Test Suite is in the current directory and subfolders.
Alternatively instead of setting a <directory> you can also specifically set the order using the <file>
tag. The order in which you define the <file> tag is the order in which the tests will be executed.
After specifying these configurations PHPUnit can find all tests automatically and you do not have
to write PHPUnit --bootstrap Bootstrap.php GridTest.php anymore.
The following command will suffice:
PHPUnit
Training Course PHPUnit Nick Belhomme 2010 p. 19
You do not have to specify the XML declaration for PHPUnit to understand it.
But as always it is best practice.
TRAINING COURSE PHPUNIT
Training Course PHPUnit Nick Belhomme 2010 p. 20
TRAINING COURSE PHPUNIT
Writing Assertions
We want to define the grid as a square which can hold Tiles. The Grid should be dynamic in size,
take actions and store a position indicating where you are on the map.
Let's first start implementing the constructor. We want it to accept the number of vertical tiles and
horizontal tiles as integers. Then we want to be able to get the grid size set.
We define the requirements by means of a Test.
<?php
public function testConstructorNormalParamsWithGetGridSize()
{
$x = rand(1,100);
$y = rand(1,100);
$grid = new Game_Grid($x,$y);
$gridSize = $grid->getGridSize();
$this->assertEquals($x, $gridSize['x']);
$this->assertEquals($y, $gridSize['y']);
$grid = new Game_Grid((string) $x, (string) $y);
$gridSize = $grid->getGridSize();
$this->assertEquals($x, $gridSize['x']);
$this->assertEquals($y, $gridSize['y']);
}
We run the test: PHPUnit
As you can see there is a fatal error: call to undefined method.
Lets implement these requirements into Game_Class
Training Course PHPUnit Nick Belhomme 2010 p. 21
TRAINING COURSE PHPUNIT
<?php
class Game_Grid
{
protected $_grid;
public function __construct($gridSizeX, $gridSizeY)
{
for ($x = 0; $x < (int) $gridSizeX; $x++) {
for ($y = 0; $y < (int) $gridSizeY; $y++) {
$this->_grid[$x][$y] = null;
}
}
}
public function getGridSize()
{
return array('x' => count($this->_grid), 'y' => count($this->_grid[0]));
}
}
Testing for Exceptions and PHP Errors
Okay so far so good. But what happens if we pass negative values or no integers at all? Time to put
it to a test and make sure how code can handle these kind of situations.
This is one of the fundamental rules in unit testing: check boundaries or abnormal situations. By
thinking of ways your application might be used and putting it thru the test, you develop more
robust and safer software. It also makes you understand your code and the requirements better.
1. Test for boundary conditions.
2. Test for both success and failure.
Training Course PHPUnit Nick Belhomme 2010 p. 22
TRAINING COURSE PHPUNIT
3. Test for general functionality.
So let us extend the testCase with new tests and some more assertions.
public function testConstructZeroParamsX()
{
$this->setExpectedException('Exception');
$grid = new Game_Grid(0,3);
}
public function testConstructZeroParamsY()
{
$this->setExpectedException('Exception');
$grid = new Game_Grid(2,0);
}
public function testConstructNegativeParamsX()
{
$this->setExpectedException('Exception');
$grid = new Game_Grid(-2,3);
}
/**
* @expectedException Exception
*/
public function testConstructNegativeParamsY()
{
$grid = new Game_Grid(2,-3);
}
Here we define the requirement to throw an Exception when a negative parameter is given.
You can tell the testing framework to expect an exception in two ways.
* By setting a DocBlock with @expectedException [exception to expect]
* By calling the setExpectedException($exceptionToExpect) method on the framework
Both work equally well but you should choose one. Remember consistency makes reading much
more comprehensible.
The test will show us that this is not yet thrown by our implementation
Training Course PHPUnit Nick Belhomme 2010 p. 23
TRAINING COURSE PHPUNIT
This will force us to write the following implementation:
public function __construct($gridSizeX, $gridSizeY)
{
if ($gridSizeX <= 0 || $gridSizeY <= 0) {
throw new Exception('The size of the Grid cannot be negative or zero');
}
for ($x = 0; $x < (int) $gridSizeX; $x++) {
for ($y = 0; $y < (int) $gridSizeY; $y++) {
$this->_grid[$x][$y] = null;
}
}
}
Training Course PHPUnit Nick Belhomme 2010 p. 24
You can also test on PHP warnings for instance when you do type hinting for
parameter checking. PHP will throw a warning when you define a function
which expects a certain type but you provide another type.
You can test that functionality with:
$this->setExpectedException('PHPUnit_Framework_Error');.
This is useful when you want your application to force the user to use a specific
type. You can check if you have put this check in place.
TRAINING COURSE PHPUNIT
This time the test will succeed and you can move on to the next assertion you can think of.
There are a lot of assertions to choose from.
A complete list can be fount here: http://www.phpunit.de/manual/current/en/api.html#api.assert
Training Course PHPUnit Nick Belhomme 2010 p. 25
TRAINING COURSE PHPUNIT
Incomplete and skipped tests
Generally when you are working on a new test case class, you might want to begin by writing
empty test methods for your public api such as:
public function testGetTileWhenNoneIsSet()
{
$this->markTestIncomplete( 'This test has not been implemented yet.' );
}
public function testGetTileFromPostionWhenNoTileIsSet()
{
$this->markTestIncomplete( 'This is a custom message.' );
}
public function testAddTile()
{
$this->markTestIncomplete( 'This test has not been implemented yet.' );
}
public function testAddTileOutsideGrid()
{
$this->markTestIncomplete( 'This test has not been implemented yet.' );
}
public function testIsOnGrid()
{
$this->markTestIncomplete( 'This test has not been implemented yet.' );
}
public function testPosition()
{
$this->markTestIncomplete( 'This test has not been implemented yet.' );
}
public function testSetPositionOutsideGrid()
{
$this->markTestIncomplete( 'This test has not been implemented yet.' );
}
public function testDefaultGridActions()
{
$this->markTestIncomplete( 'This test has not been implemented yet.' );
}
public function testAddActionAndGetActions()
{
$this->markTestIncomplete( 'This test has not been implemented yet.' );
}
This practice will make sure you do not forget to test some of your public api methods. PHPUnit
will tell you on each run you did not yet implement the required test.
Training Course PHPUnit Nick Belhomme 2010 p. 26
TRAINING COURSE PHPUNIT
Having an empty tests will not suffice.
public function testSomething()
{
}
Empty tests have the problem that they are interpreted as a success by the PHPUnit framework.
This misinterpretation leads to the test reports being useless -- you cannot see whether a test is
actually successful or just not yet implemented. Calling $this->fail() in the unimplemented test
method does not help either, since then the test will be interpreted as a failure. This would be just as
wrong as interpreting an unimplemented test as a success.
To indicate that a certain test should be skipped we can use the method
$this->markTestSkipped( 'The MySQLi extension is not available.' );
This is useful when some precondition is not met. For instance a mySQLi extention is not available.
The descriptive messages passed as a parameter can be any string you like.
Training Course PHPUnit Nick Belhomme 2010 p. 27
TRAINING COURSE PHPUNIT
Test Doubles
We are going to add Tiles to our Game_Grid. First we add tests for getting the Tile.
public function testGetTileWhenNoneIsSet()
{
$grid = new Game_Grid(1,1);
$this->assertNull($grid->getTile(0,0));
}
public function testGetTileOutsideGrid()
{
$this->setExpectedException('Exception');
$grid = new Game_Grid(1,1);
$this->assertNull($grid->getTile(3,0));
}
When we have implemented our Game_Grid class to make the tests OK we can proceed to add
Tiles. How can we test this when the Game_Tile class hasn't been written yet? For this reason
amongst others we have Mocks at our disposal. They are very powerful.
public function testAddTile()
{
$tile = $this->getMock('stdClass', null, array(), 'Game_Tile');
$grid = new Game_Grid(1,1);
$this->assertType('Game_Grid', $grid->addTile($tile, 0, 0));
$this->assertType('Game_Tile', $grid->getTile(0,0));
}
At the moment there is no public api yet implemented for Game_Tile so Game_Grid cannot be
aware of it and thus not use it.
Mocking from stdClass will not work if there is interaction with Game_Tile from within the method
Game_Grid::addTile(). When such an implementation is needed we will need to create the
Game_Tile class and implement the called methods, properties or whatever is needed. Then we can
use the Game_Tile class as a blueprint like we did with stdClass. Making sure no real actions are
performed when called upon.
“Sometimes it is just plain hard to test the system under test (SUT) because it depends on other
components that cannot be used in the test environment. This could be because they aren't
available, they will not return the results needed for the test or because executing them would have
undesirable side effects. In other cases, our test strategy requires us to have more control or
visibility of the internal behavior of the SUT.
When we are writing a test in which we cannot (or chose not to) use a real depended-on component
(DOC), we can replace it with a Test Double. The Test Double doesn't have to behave exactly like
the real DOC; it merely has to provide the same API as the real one so that the SUT thinks it is the
real one! [Gerard Meszaros - Meszaros2007] “
Training Course PHPUnit Nick Belhomme 2010 p. 28
TRAINING COURSE PHPUNIT
$this->getMock() method of PHPUnit accepts several parameters.
protected function getMock($originalClassName, $methods = array(), array
$arguments = array(), $mockClassName = '', $callOriginalConstructor = TRUE,
$callOriginalClone = TRUE, $callAutoload = TRUE)
As you can see only the first parameter is mandatory all others are optional.
The first parameter is the name of an existing class needed to be mocked. PHPUnit will replace the
implementation from all the methods from this class with “return null;” unless otherwise stated
in parameter two or by further configuration of the mock object returned. The only method that will
remain unchanged by default is the constructor which can also be mocked by setting the fifth
parameter to false.
As we said parameter two gives you the possibility to specify only those methods for which you
want PHPUnit to change the implementation. Only the methods whose names are in the array are
replaced with a configurable test double. The behavior of the other methods is not changed.
The third parameter may hold a parameter array that is passed to the original class' constructor.
The fourth parameter can be used to specify a class name for the generated test double class.
The fifth parameter can be used to disable the call to the original class' constructor.
The sixth parameter can be used to disable the call to the original class' clone constructor
The seventh parameter can be used to disable __autoload() during the generation of the test
double class.
After implementing the tile functionality into the grid, we also need to implement the actions that
can be set. We want to be able to move from tile to tile on the grid. This means we have to be able
to add and get actions from Game_Grid. The tricky part in this implementation is that Game_Action
should be aware on which Game_Item, Game_Tile and or Game_Grid object it is acting it's magic
on. This was defined in the project requirements. This implies that when adding an action to the grid
the grid sets itself as a subject to the action. (see class diagram)
Stubbing is the practice of replacing an object with a test double that (optionally) returns configured
return values. You can use a stub to "replace a real component on which the SUT depends
so that the test has a control point for the indirect inputs of the SUT. This allows the test to force the
SUT down paths it might not otherwise execute".
Our implementation of Game_Action will be an abstract one. We could make it a fully implemented
class which accepts a name by a setter. But we do not want that, because we feel like every action in
the game should have a real hardcoded implementation and not generated on the fly. Of course this
can change in time. In example future implementations may come from a CMS tool and then we
will need the setters. But for now we do not want a mix of real objects with objects generated on the
Training Course PHPUnit Nick Belhomme 2010 p. 29
TRAINING COURSE PHPUNIT
fly.
First let us build the tests for Game_Action.
To test Game_Action we will use the PHPUnit method
PHPUnit_Testframework_TestCase::getMockForAbstractClass().
protected function getMockForAbstractClass($originalClassName, array $arguments
= array(), $mockClassName = '', $callOriginalConstructor = TRUE,
$callOriginalClone = TRUE, $callAutoload = TRUE)
Which will instantiate an abstract class with a test double. Making sure our abstract execute is
implemented.
Tests/PHPUnit/Library/Game/ActionTest.php
<?php
class Game_ActionTest extends PHPUnit_Framework_TestCase
{
public function setUp()
{
$this->_action = $this->getMockForAbstractClass('Game_Action');
}
public function testConstructorWithParams()
{
$grid = $this->getMock('Game_Grid', null, array(1,1));
$inventory = $this->getMock('Game_Inventory', null, array());
$this->assertType('Game_Action',
$this->getMockForAbstractClass('Game_Action', array($grid, $inventory)));
$this->assertType('Game_Action',
$this->getMockForAbstractClass('Game_Action', array($grid)));
$this->assertType('Game_Action',
$this->getMockForAbstractClass('Game_Action', array(null, $inventory)));
$this->assertType('Game_Action',
$this->getMockForAbstractClass('Game_Action', array(null, null)));
}
public function testConstructorWithParamsNotGrid()
{
$this->setExpectedException('PHPUnit_Framework_Error');
$this->assertType('Game_Action',
$this->getMockForAbstractClass('Game_Action', array('blabla')));
}
public function testConstructorWithParamsNotInventory()
{
$this->setExpectedException('PHPUnit_Framework_Error');
$this->assertType('Game_Action',
$this->getMockForAbstractClass('Game_Action', array(null, 'blabla')));
}
Training Course PHPUnit Nick Belhomme 2010 p. 30
TRAINING COURSE PHPUNIT
public function testSetItem()
{
require_once 'Item/Stub.php';
$item = new Game_Item_Stub();
$this->assertType('Game_Action', $this->_action->setSubject($item));
$tile = $this->getMock('Game_Tile');
$this->assertType('Game_Action', $this->_action->setSubject($tile));
$grid = $this->getMock('Game_Grid', null, array(1,1));
$this->assertType('Game_Action', $this->_action->setSubject($grid));
}
public function testSetItemIncorrectParam()
{
$this->setExpectedException('Exception');
$this->assertType('Game_Action', $this->_action->setSubject('blabla'));
}
public function testGetName()
{
$this->assertEquals('action', $this->_action->getName());
}
public function testGetSynonyms()
{
$this->assertType('array', $this->_action->getSynonyms());
}
public function testSetGrid()
{
$grid = $this->getMock('Game_Grid', null, array(1,1));
$this->assertType('Game_Action', $this->_action->setGrid($grid));
}
public function testSetGridWithFaultyParam()
{
$this->setExpectedException('PHPUnit_Framework_Error');
$this->_action->setGrid('wrong param');
}
public function testSetPersonalInventory()
{
$inventory = $this->getMock('Game_Inventory');
$this->assertType('Game_Action',
$this->_action->setPersonalInventory($inventory));
}
public function testSetPersonalInventoryWithFaultyParam()
{
$this->setExpectedException('PHPUnit_Framework_Error');
$this->_action->setPersonalInventory('wrong param');
}
}
Training Course PHPUnit Nick Belhomme 2010 p. 31
TRAINING COURSE PHPUNIT
Library/Game/Action.php
<?php
abstract class Game_Action
{
protected $_subject;
protected $_name = 'action';
protected $_synonyms = array();
protected $_grid;
protected $_personalInventory;
public function __construct(Game_Grid $grid = null,
Game_Inventory $inventory = null)
{
$this->_grid = $grid;
$this->_personalInventory = $inventory;
$this->_init();
}
public function setSubject($subject)
{
if (!($subject instanceOf Game_Item) && !($subject instanceOf Game_Tile)
&& !($subject instanceOf Game_Grid)) {
throw new Exception('Subject passed should be of type Game_subject,
Game_Tile, Game_Grid');
}
$this->_subject = $subject;
return $this;
}
public function getName()
{
return $this->_name;
}
public function getSynonyms()
{
return $this->_synonyms;
}
public function setGrid(Game_Grid $grid)
{
$this->_grid = $grid;
return $this;
}
public function setPersonalInventory(Game_Inventory $inventory)
{
$this->_personalInventory = $inventory;
return $this;
}
abstract public function execute();
protected function _getExecutedMessageSuccess()
{
Training Course PHPUnit Nick Belhomme 2010 p. 32
TRAINING COURSE PHPUNIT
echo 'action executed';
}
protected function _getExecutedMessageFailed()
{
echo 'action failed';
}
protected function _executeSuccess()
{
// template method
}
protected function _executeFailed()
{
// template method
}
protected function _init()
{
// template method
}
}
Now that Game_Action is implemented and tested it is time to create a helper stub. Because all the
objects depending on actions need an action name to differentiate between the different actions by
use of array keys, we will create a stub that will extend Game_Action and implement a setter.
Game_Grid is such an object. We want to give it four moving actions: GoNorth, goSouth, goWest,
goEast. To do this we must be able to change the name of the action.
The stub Game_Action_Stub will extend the Game_Action abstract class, set the default name and
add a setter.
Tests/PHPUnit/Library/Game/Action/Stub.php
<?php
class Game_Action_Stub extends Game_Action
{
public function execute()
{
return null;
}
// helper function for testing
public function setName($name)
{
$this->_name = $name;
}
}
Training Course PHPUnit Nick Belhomme 2010 p. 33
TRAINING COURSE PHPUNIT
After having Game_Action in place we can continue testing Game_Grid.
public function testAddActionAndGetActions()
{
require_once './Action/Stub.php';
$action = $this->getMock('Game_Action_Stub');
$action->expects($this->once())
->method('setGrid')
->with($this->isInstanceOf('Game_Grid'));
$action->expects($this->once())
->method('getName')
->will($this->returnValue('stubAction'));
$grid = new Game_Grid(2,2);
$actions = $grid->getActions();
$initialActionsCount = count($actions);
$this->assertType('Game_Grid', $grid->addAction($action));
$actions = $grid->getActions();
$this->assertEquals($initialActionsCount+1, count($actions));
// Same name, it will overwrite the previous one set
$action2 = $this->getMock('Game_Action');
$action2->expects($this->once())
->method('getName')
->will($this->returnValue('stubAction'));
$this->assertType('Game_Grid', $grid->addAction($action2));
$actions = $grid->getActions();
$this->assertEquals($initialActionsCount+1, count($actions));
}
We define in the test:
1) there should be a method Game_Grid::addAction and it should make one call
to the method setGrid on the passed Game_Action instance passing the
Game_Grid as a param.
2) Secondly it also should make a call to Game_Action::getName() and this
method will return the string 'stubAction'. Thus forcing Game_Grid to
accept a string value. Which it will actually use as the key in the
$actions array.
3) It also should have a fluent interface so the return value of addAction
should be Game_Grid itself.
4) Adding a new action will resolve in one action registered. Adding a second
with the same name will overwrite the previous one added. This requirement
is tested by creating another action with the same name and adding it to
the Grid.
public function addAction(Game_Action $action)
{
$this->_actions[$action->getName()] = $action;
$action->setGrid($this);
return $this;
}
Training Course PHPUnit Nick Belhomme 2010 p. 34
TRAINING COURSE PHPUNIT
Training Course PHPUnit Nick Belhomme 2010 p. 35
You cannot test the call to Game_Action::setGrid if you use
getMockForAbstractClass(). This method replaces the abstract methods by a
default implementation, leaving the others unchanged. It will always return
failure because it cannot register the call. Thus we use getMock() which needs
a non abstract class. Luckily for us we already defined the helper stub class
earlier.
TRAINING COURSE PHPUNIT
Dependencies
Tests/PHPUnit/Library/Game/GridTest.php
<?php
class Game_GridTest extends PHPUnit_Framework_TestCase
{
public function testConstructorNormalParamsWithGetGridSize()
{
$x = rand(1,100);
$y = rand(1,100);
$grid = new Game_Grid($x,$y);
$gridSize = $grid->getGridSize();
$this->assertEquals($x, $gridSize['x']);
$this->assertEquals($y, $gridSize['y']);
$grid = new Game_Grid((string) $x, (string) $y);
$gridSize = $grid->getGridSize();
$this->assertEquals($x, $gridSize['x']);
$this->assertEquals($y, $gridSize['y']);
}
public function constructorProviderBadParams()
{
return array(
array(0, 0),
array(0, 1),
array(1, 0),
array(-1, 1),
array(1, -1),
array(-1, -1),
);
}
/**
*
* @dataProvider constructorProviderBadParams
*/
public function testConstructNegativeOrZeroParam($paramX, $paramY)
{
$this->setExpectedException('Exception');
$grid = new Game_Grid($paramX, $paramY);
}
public function testGetTileWhenNoneIsSet()
{
$grid = new Game_Grid(1,1);
$this->assertNull($grid->getTile(0,0));
}
public function testGetTileFromPostionWhenNoTileIsSet()
{
$grid = new Game_Grid(1,1);
$this->assertNull($grid->getTileFromPosition());
}
public function testAddTile()
Training Course PHPUnit Nick Belhomme 2010 p. 36
TRAINING COURSE PHPUNIT
{
$tile = $this->getMock('Game_Tile', null, array(), 'Game_Tile_Mock',
false);
$grid = new Game_Grid(1,1);
$this->assertType('Game_Grid', $grid->addTile($tile, 0, 0));
$this->assertType('Game_Tile_Mock', $grid->getTile(0,0));
}
public function testAddTileOutsideGrid()
{
$this->setExpectedException('Exception');
$tile = $this->getMock('Game_Tile', null, array(), 'Game_Tile_Mock',
false);
$grid = new Game_Grid(1,1);
$grid->addTile($tile, 2, 3);
}
public function testIsOnGrid()
{
$grid = new Game_Grid(1,1);
$this->assertTrue($grid->isOnGrid(0,0));
$this->assertFalse($grid->isOnGrid(0,1));
}
public function testPosition()
{
$x = rand(1,100);
$y = rand(1,100);
$grid = new Game_Grid($x,$y);
$position = $grid->getPosition();
$this->assertEquals(0, $position['x']);
$this->assertEquals(0, $position['y']);
$newPositionX = rand(0,$x-1);
$newPositionY = rand(0,$y-1);
$grid->setPosition($newPositionX, $newPositionY);
$position = $grid->getPosition();
$this->assertEquals($newPositionX, $position['x']);
$this->assertEquals($newPositionY, $position['y']);
}
public function testSetPositionOutsideGrid()
{
$this->setExpectedException('Exception');
$grid = new Game_Grid(1,1);
$grid->setPosition(3,2);
}
public function testDefaultGridActions()
{
$grid = new Game_Grid(2,2);
$actions = $grid->getActions();
$this->assertEquals(4, count($actions));
$gameActionGoNorthFound = false;
$gameActionGoEastFound = false;
$gameActionGoSouthFound = false;
Training Course PHPUnit Nick Belhomme 2010 p. 37
TRAINING COURSE PHPUNIT
$gameActionGoWestFound = false;
foreach ($actions as $action) {
if ($action instanceof Game_Action_Go_North) {
$gameActionGoNorthFound = true;
}
if ($action instanceof Game_Action_Go_East) {
$gameActionGoEastFound = true;
}
if ($action instanceof Game_Action_Go_South) {
$gameActionGoSouthFound = true;
}
if ($action instanceof Game_Action_Go_West) {
$gameActionGoWestFound = true;
}
}
$this->assertTrue($gameActionGoNorthFound);
$this->assertTrue($gameActionGoEastFound);
$this->assertTrue($gameActionGoSouthFound);
$this->assertTrue($gameActionGoWestFound);
return $grid;
}
/**
* this dependency has been added to show this functionality
* @depends testDefaultGridActions
*/
public function testAddActionAndGetActions(Game_Grid $grid)
{
require_once './Action/Stub.php';
$action = $this->getMock('Game_Action_Stub');
$action->expects($this->once())
->method('setGrid')
->with($this->isInstanceOf('Game_Grid'));
$action->expects($this->once())
->method('getName')
->will($this->returnValue('stubAction'));
$actions = $grid->getActions();
$this->assertEquals(4, count($actions));
$this->assertType('Game_Grid', $grid->addAction($action));
$actions = $grid->getActions();
$this->assertEquals(5, count($actions));
// Same name, it will overwrite the previous one set
$action2 = $this->getMock('Game_Action');
$action2->expects($this->once())
->method('getName')
->will($this->returnValue('stubAction'));
$this->assertType('Game_Grid', $grid->addAction($action2));
$actions = $grid->getActions();
$this->assertEquals(5, count($actions));
}
}
Normally every test should be independent from each other. And should be able to be executed in
random order. Yet sometimes to quickly localize defects, we want our attention to be focused on
Training Course PHPUnit Nick Belhomme 2010 p. 38
TRAINING COURSE PHPUNIT
relevant failing tests. This is why PHPUnit skips the execution of a test when a depended-upon test
has failed. This improves defect localization by exploiting the dependencies between tests as shown
in the above testcase, “Exploiting the dependencies between tests”.
Purely functioning as an example we have changed some tests in the above code.
The test testAddActionAndGetActions depends on testDefaultGridActions.
Because if the defaultGridActions are not set correctly then our assertEquals for adding and getting
will also fail for testAddActionAndGetActions. We can define such a dependency by using a doc
block annotation @depends [test on which the test is dependant].
We have the possibility to pass fixtures as params in such cases, making sure that the test
environment is correctly set.
For the above dependency, testDefaultGridActions is called the producer and
testAddActionAndGetActions is called the consumer.
The DataProvider
Sometimes we want to test the same method with different params which should all result in the an
outcome predicted by the test. To facilitate this a dataProvider has been made available.
In the code in the previous chapter we have rewritten the tests from the chapter: “Testing for
Exceptions and PHP Errors” to use the dataProvider annotation. This will make our testing much
more easy to understand and extend. Because it removes redundancy.
For each array that is part of the dataProvider collection the test method will be called with the
contents of the array as its arguments.
You create a public method which returns an array or an object which implements the Iterator
interface. This method is called the dataProvider and you link it to the testmethod with a doc block
@dataProvider [provider method]
Training Course PHPUnit Nick Belhomme 2010 p. 39
A producer is a test method that yields its unit under test as return value.
A consumer is a test method that depends on one or more producers and their
return values.
PHPUnit does not change the order in which tests are executed, you have to
ensure that the dependencies of a test can actually be met before the test is run.
TRAINING COURSE PHPUNIT
Training Course PHPUnit Nick Belhomme 2010 p. 40
When a test receives input from both a @dataProvider method and from one or
more tests it @depends on, the arguments from the data provider will come
before the ones from depended-upon tests.
TRAINING COURSE PHPUNIT
setUp, tearDown and other template methods
One of the most time-consuming parts of writing tests is writing the code to setup the world to a
known state and then return it to its original state when the test is complete. This known state is
called the fixture of the test.
Setting and destructing a fixture for every test can be time consuming or a lot of copy and pasting,
the latter being against good coding practice. DRY (Do Not Repeat yourself) is something every
programmer should try to adhere to.
PHPUnit has made it easy for us to to provide us with hooks in the form of template methods.
Template methods are empty methods which are called in a predefined algorithm set by the class
which owns the template methods. You may give the algorithm a specific implementation by
implementing the methods.
Template methods available in order of execution.
• setUpBeforeClass():
Each time when your testcase class is run this method will be called once.
Here you can create for example a file with content that will not change in the life cycle of
the tests. And there for is not needed to be created every time between tests.
• setUp():
Before a test method is run, a template method called setUp() is invoked. setUp() is where
you create the objects against which you will test.
• assertPreConditions():
After each setup but before each test, this method is called. Here you can implement a check
on certain aspects of your testing stage.
[THE ACTUAL TEST WILL BE EXECUTED HERE]
• assertPostConditions():
After each test executed, this method is called. Here you can implement a check on certain
aspects of your testing stage.
• tearDown():
Once the test method has finished running, whether it succeeded or failed, another template
method called tearDown() is invoked. tearDown() is where you clean allocated external
resources like files or sockets. Also clean up objects for garbage collection.
• tearDownAfterClass():
Each time when your testcase class has finished running all tests and template methods this
last method will be called. This method will be called once. Here you can delete for example
the file that was set in the setupBeforeClass().
Training Course PHPUnit Nick Belhomme 2010 p. 41
TRAINING COURSE PHPUNIT
Testing your tests, code coverage
After creating a test for some functionality you should test the testcase for code coverage. What use
do tests have if they do not cover all of your application code. They will provide you with a false
sense of safety. Thus it is very important to tests your tests.
Luckily for us, PHPUnit has such a feature embedded into it's service. It uses the Xdebug extension.
PHPUnit can output code coverage in the following formats:
• html: coverage report in HTML format
phpunit --coverage-html <dir>
• clover: code coverage data in Clover XML format
phpunit --coverage-clover <file>
• source: code coverage / source data in XML format
phpunit --coverage-source <dir>
To produce a code coverage test for ActionTest use the following command.
phpunit --coverage-html /tmp/PHPUnit/Coverage/ ActionTest
We have already defined the setup for the code coverage in our configuration file from chapter
“Configure PHPUnit with a phpunit.xml configuration file”
<logging>
<log type="coverage-html" target="/tmp/PHPUnit/Coverage/"
charset="UTF-8" yui="true", highlight="false" lowUpperBound="35"
highLowerBound="70" />
</logging>
The <logging> element and its <log> children can be used to configure the logging of the test
execution.
type: specifies the type of logging
target: the target directory where the log should be written to
charset: the character encoding for the log
yui: Yahoo User Interface, true enables javascript on click events.
Highlight: activates the code syntax highlighting.
lowUpperBound: overwrite the default percentage of 35 to your upper limit of max coverage
percentage that is needed to qualiy as low.
HighLowerBound: overwrite the default percentage of 70 to your minimum coverage percentage is
needed to qualify as high coverage.
Training Course PHPUnit Nick Belhomme 2010 p. 42
TRAINING COURSE PHPUNIT
Increase the readability by creating smaller tests and adding
assertion messages.
Want to know more?
Want to learn about all the nitty gritty hidden features
of PHPUnit?
Want to integrate PHPUnit in your IDE?
Want to integrate PHPUnit in your ZF projects?
Want to fully automate the running of tests with continuous
integration?
GOOD NEWS, YOU CAN!
• I give workshops at community events for free (technically
they are sponsored)
• I can give a workshop at the company for which you work.
(Your boss pays and during office hours you get to become a
PHPUnit expert, pretty neat I would say)
• Purchase my upcoming book on an important framework.
THANK YOU FOR READING,
Nick Belhomme
follow me on twitter: @NickBelhomme
http://nickbelhomme.com/phpunit-training
Training Course PHPUnit Nick Belhomme 2010 p. 43

Mais conteúdo relacionado

Mais procurados

Learning git
Learning gitLearning git
Learning gitSid Anand
 
A Practical Introduction to git
A Practical Introduction to gitA Practical Introduction to git
A Practical Introduction to gitEmanuele Olivetti
 
How Node.js Works | Node.js Tutorial for Beginners
How Node.js Works | Node.js Tutorial for BeginnersHow Node.js Works | Node.js Tutorial for Beginners
How Node.js Works | Node.js Tutorial for BeginnersIntellipaat
 
Git Started With Git
Git Started With GitGit Started With Git
Git Started With GitNick Quaranto
 
Git One Day Training Notes
Git One Day Training NotesGit One Day Training Notes
Git One Day Training Notesglen_a_smith
 
Dealing with Merge Conflicts in Git
Dealing with Merge Conflicts in GitDealing with Merge Conflicts in Git
Dealing with Merge Conflicts in Gitgittower
 
Linux booting process - Linux System Administration
Linux booting process - Linux System AdministrationLinux booting process - Linux System Administration
Linux booting process - Linux System AdministrationSreenatha Reddy K R
 
Minicurso GIT Completo (2022)
Minicurso GIT Completo (2022)Minicurso GIT Completo (2022)
Minicurso GIT Completo (2022)Danilo Pinotti
 
Git pour les (pas si) nuls
Git pour les (pas si) nulsGit pour les (pas si) nuls
Git pour les (pas si) nulsMalk Zameth
 
Github - Git Training Slides: Foundations
Github - Git Training Slides: FoundationsGithub - Git Training Slides: Foundations
Github - Git Training Slides: FoundationsLee Hanxue
 
Understanding Branching and Merging in Git
Understanding Branching and Merging in GitUnderstanding Branching and Merging in Git
Understanding Branching and Merging in Gitgittower
 
Spring Certification Questions
Spring Certification QuestionsSpring Certification Questions
Spring Certification QuestionsSpringMockExams
 
Debugging Applications with GNU Debugger
Debugging Applications with GNU DebuggerDebugging Applications with GNU Debugger
Debugging Applications with GNU DebuggerPriyank Kapadia
 
Javascript Prototype Visualized
Javascript Prototype VisualizedJavascript Prototype Visualized
Javascript Prototype Visualized军 沈
 

Mais procurados (20)

Learning git
Learning gitLearning git
Learning git
 
Git and github 101
Git and github 101Git and github 101
Git and github 101
 
Git & git hub
Git & git hubGit & git hub
Git & git hub
 
A Practical Introduction to git
A Practical Introduction to gitA Practical Introduction to git
A Practical Introduction to git
 
Spring Batch Avance
Spring Batch AvanceSpring Batch Avance
Spring Batch Avance
 
How Node.js Works | Node.js Tutorial for Beginners
How Node.js Works | Node.js Tutorial for BeginnersHow Node.js Works | Node.js Tutorial for Beginners
How Node.js Works | Node.js Tutorial for Beginners
 
Git Started With Git
Git Started With GitGit Started With Git
Git Started With Git
 
Git One Day Training Notes
Git One Day Training NotesGit One Day Training Notes
Git One Day Training Notes
 
Dealing with Merge Conflicts in Git
Dealing with Merge Conflicts in GitDealing with Merge Conflicts in Git
Dealing with Merge Conflicts in Git
 
Linux booting process - Linux System Administration
Linux booting process - Linux System AdministrationLinux booting process - Linux System Administration
Linux booting process - Linux System Administration
 
Introduction to Java
Introduction to JavaIntroduction to Java
Introduction to Java
 
Minicurso GIT Completo (2022)
Minicurso GIT Completo (2022)Minicurso GIT Completo (2022)
Minicurso GIT Completo (2022)
 
Unit Test
Unit TestUnit Test
Unit Test
 
Git pour les (pas si) nuls
Git pour les (pas si) nulsGit pour les (pas si) nuls
Git pour les (pas si) nuls
 
Github - Git Training Slides: Foundations
Github - Git Training Slides: FoundationsGithub - Git Training Slides: Foundations
Github - Git Training Slides: Foundations
 
Understanding Branching and Merging in Git
Understanding Branching and Merging in GitUnderstanding Branching and Merging in Git
Understanding Branching and Merging in Git
 
Optimizing Your CI Pipelines
Optimizing Your CI PipelinesOptimizing Your CI Pipelines
Optimizing Your CI Pipelines
 
Spring Certification Questions
Spring Certification QuestionsSpring Certification Questions
Spring Certification Questions
 
Debugging Applications with GNU Debugger
Debugging Applications with GNU DebuggerDebugging Applications with GNU Debugger
Debugging Applications with GNU Debugger
 
Javascript Prototype Visualized
Javascript Prototype VisualizedJavascript Prototype Visualized
Javascript Prototype Visualized
 

Semelhante a Cursus phpunit

PHPUnit with Magento
PHPUnit with MagentoPHPUnit with Magento
PHPUnit with MagentoTu Hoang
 
Test Driven Development (TDD) Preso 360|Flex 2010
Test Driven Development (TDD) Preso 360|Flex 2010Test Driven Development (TDD) Preso 360|Flex 2010
Test Driven Development (TDD) Preso 360|Flex 2010guest5639fa9
 
Test Driven Development (TDD) with FlexUnit 4 - 360|Flex San Jose preso
Test Driven Development (TDD) with FlexUnit 4 - 360|Flex San Jose presoTest Driven Development (TDD) with FlexUnit 4 - 360|Flex San Jose preso
Test Driven Development (TDD) with FlexUnit 4 - 360|Flex San Jose presoElad Elrom
 
Unit Tests? It is Very Simple and Easy!
Unit Tests? It is Very Simple and Easy!Unit Tests? It is Very Simple and Easy!
Unit Tests? It is Very Simple and Easy!Return on Intelligence
 
Unit testing php-unit - phing - selenium_v2
Unit testing   php-unit - phing - selenium_v2Unit testing   php-unit - phing - selenium_v2
Unit testing php-unit - phing - selenium_v2Tricode (part of Dept)
 
Put an end to regression with codeception testing
Put an end to regression with codeception testingPut an end to regression with codeception testing
Put an end to regression with codeception testingJoe Ferguson
 
Test Driven Development
Test Driven DevelopmentTest Driven Development
Test Driven Developmentbhochhi
 
Testing practicies not only in scala
Testing practicies not only in scalaTesting practicies not only in scala
Testing practicies not only in scalaPaweł Panasewicz
 
PHPUnit: from zero to hero
PHPUnit: from zero to heroPHPUnit: from zero to hero
PHPUnit: from zero to heroJeremy Cook
 
Agile Values, Principles and Practices
Agile Values, Principles and PracticesAgile Values, Principles and Practices
Agile Values, Principles and Practicesjackcrews
 
Test-Driven Development In Action
Test-Driven Development In ActionTest-Driven Development In Action
Test-Driven Development In ActionJon Kruger
 
Test Driven Development - Overview and Adoption
Test Driven Development - Overview and AdoptionTest Driven Development - Overview and Adoption
Test Driven Development - Overview and AdoptionPyxis Technologies
 
assertYourself - Breaking the Theories and Assumptions of Unit Testing in Flex
assertYourself - Breaking the Theories and Assumptions of Unit Testing in FlexassertYourself - Breaking the Theories and Assumptions of Unit Testing in Flex
assertYourself - Breaking the Theories and Assumptions of Unit Testing in Flexmichael.labriola
 
Developers’ mDay u Banjoj Luci - Milan Popović, PHP Srbija – Testimony (about...
Developers’ mDay u Banjoj Luci - Milan Popović, PHP Srbija – Testimony (about...Developers’ mDay u Banjoj Luci - Milan Popović, PHP Srbija – Testimony (about...
Developers’ mDay u Banjoj Luci - Milan Popović, PHP Srbija – Testimony (about...mCloud
 

Semelhante a Cursus phpunit (20)

PHPUnit
PHPUnitPHPUnit
PHPUnit
 
TDD - Agile
TDD - Agile TDD - Agile
TDD - Agile
 
PHPUnit with Magento
PHPUnit with MagentoPHPUnit with Magento
PHPUnit with Magento
 
Test Driven Development (TDD) Preso 360|Flex 2010
Test Driven Development (TDD) Preso 360|Flex 2010Test Driven Development (TDD) Preso 360|Flex 2010
Test Driven Development (TDD) Preso 360|Flex 2010
 
Test Driven Development (TDD) with FlexUnit 4 - 360|Flex San Jose preso
Test Driven Development (TDD) with FlexUnit 4 - 360|Flex San Jose presoTest Driven Development (TDD) with FlexUnit 4 - 360|Flex San Jose preso
Test Driven Development (TDD) with FlexUnit 4 - 360|Flex San Jose preso
 
Python and test
Python and testPython and test
Python and test
 
Tdd
TddTdd
Tdd
 
Why test with flex unit
Why test with flex unitWhy test with flex unit
Why test with flex unit
 
TDD Workshop UTN 2012
TDD Workshop UTN 2012TDD Workshop UTN 2012
TDD Workshop UTN 2012
 
Unit Tests? It is Very Simple and Easy!
Unit Tests? It is Very Simple and Easy!Unit Tests? It is Very Simple and Easy!
Unit Tests? It is Very Simple and Easy!
 
Unit testing php-unit - phing - selenium_v2
Unit testing   php-unit - phing - selenium_v2Unit testing   php-unit - phing - selenium_v2
Unit testing php-unit - phing - selenium_v2
 
Put an end to regression with codeception testing
Put an end to regression with codeception testingPut an end to regression with codeception testing
Put an end to regression with codeception testing
 
Test Driven Development
Test Driven DevelopmentTest Driven Development
Test Driven Development
 
Testing practicies not only in scala
Testing practicies not only in scalaTesting practicies not only in scala
Testing practicies not only in scala
 
PHPUnit: from zero to hero
PHPUnit: from zero to heroPHPUnit: from zero to hero
PHPUnit: from zero to hero
 
Agile Values, Principles and Practices
Agile Values, Principles and PracticesAgile Values, Principles and Practices
Agile Values, Principles and Practices
 
Test-Driven Development In Action
Test-Driven Development In ActionTest-Driven Development In Action
Test-Driven Development In Action
 
Test Driven Development - Overview and Adoption
Test Driven Development - Overview and AdoptionTest Driven Development - Overview and Adoption
Test Driven Development - Overview and Adoption
 
assertYourself - Breaking the Theories and Assumptions of Unit Testing in Flex
assertYourself - Breaking the Theories and Assumptions of Unit Testing in FlexassertYourself - Breaking the Theories and Assumptions of Unit Testing in Flex
assertYourself - Breaking the Theories and Assumptions of Unit Testing in Flex
 
Developers’ mDay u Banjoj Luci - Milan Popović, PHP Srbija – Testimony (about...
Developers’ mDay u Banjoj Luci - Milan Popović, PHP Srbija – Testimony (about...Developers’ mDay u Banjoj Luci - Milan Popović, PHP Srbija – Testimony (about...
Developers’ mDay u Banjoj Luci - Milan Popović, PHP Srbija – Testimony (about...
 

Mais de Nick Belhomme

Vagrant move over, here is Docker
Vagrant move over, here is DockerVagrant move over, here is Docker
Vagrant move over, here is DockerNick Belhomme
 
Mastering selenium for automated acceptance tests
Mastering selenium for automated acceptance testsMastering selenium for automated acceptance tests
Mastering selenium for automated acceptance testsNick Belhomme
 
PHP traits, treat or threat?
PHP traits, treat or threat?PHP traits, treat or threat?
PHP traits, treat or threat?Nick Belhomme
 
PHP Quality Assurance Workshop PHPBenelux
PHP Quality Assurance Workshop PHPBeneluxPHP Quality Assurance Workshop PHPBenelux
PHP Quality Assurance Workshop PHPBeneluxNick Belhomme
 
Mastering Namespaces in PHP
Mastering Namespaces in PHPMastering Namespaces in PHP
Mastering Namespaces in PHPNick Belhomme
 
Zend Framework Form: Mastering Decorators
Zend Framework Form: Mastering DecoratorsZend Framework Form: Mastering Decorators
Zend Framework Form: Mastering DecoratorsNick Belhomme
 
Zend Framework 1.8 workshop
Zend Framework 1.8 workshopZend Framework 1.8 workshop
Zend Framework 1.8 workshopNick Belhomme
 

Mais de Nick Belhomme (7)

Vagrant move over, here is Docker
Vagrant move over, here is DockerVagrant move over, here is Docker
Vagrant move over, here is Docker
 
Mastering selenium for automated acceptance tests
Mastering selenium for automated acceptance testsMastering selenium for automated acceptance tests
Mastering selenium for automated acceptance tests
 
PHP traits, treat or threat?
PHP traits, treat or threat?PHP traits, treat or threat?
PHP traits, treat or threat?
 
PHP Quality Assurance Workshop PHPBenelux
PHP Quality Assurance Workshop PHPBeneluxPHP Quality Assurance Workshop PHPBenelux
PHP Quality Assurance Workshop PHPBenelux
 
Mastering Namespaces in PHP
Mastering Namespaces in PHPMastering Namespaces in PHP
Mastering Namespaces in PHP
 
Zend Framework Form: Mastering Decorators
Zend Framework Form: Mastering DecoratorsZend Framework Form: Mastering Decorators
Zend Framework Form: Mastering Decorators
 
Zend Framework 1.8 workshop
Zend Framework 1.8 workshopZend Framework 1.8 workshop
Zend Framework 1.8 workshop
 

Último

The Future of Software Development - Devin AI Innovative Approach.pdf
The Future of Software Development - Devin AI Innovative Approach.pdfThe Future of Software Development - Devin AI Innovative Approach.pdf
The Future of Software Development - Devin AI Innovative Approach.pdfSeasiaInfotech2
 
Unleash Your Potential - Namagunga Girls Coding Club
Unleash Your Potential - Namagunga Girls Coding ClubUnleash Your Potential - Namagunga Girls Coding Club
Unleash Your Potential - Namagunga Girls Coding ClubKalema Edgar
 
DevEX - reference for building teams, processes, and platforms
DevEX - reference for building teams, processes, and platformsDevEX - reference for building teams, processes, and platforms
DevEX - reference for building teams, processes, and platformsSergiu Bodiu
 
"Federated learning: out of reach no matter how close",Oleksandr Lapshyn
"Federated learning: out of reach no matter how close",Oleksandr Lapshyn"Federated learning: out of reach no matter how close",Oleksandr Lapshyn
"Federated learning: out of reach no matter how close",Oleksandr LapshynFwdays
 
What's New in Teams Calling, Meetings and Devices March 2024
What's New in Teams Calling, Meetings and Devices March 2024What's New in Teams Calling, Meetings and Devices March 2024
What's New in Teams Calling, Meetings and Devices March 2024Stephanie Beckett
 
SAP Build Work Zone - Overview L2-L3.pptx
SAP Build Work Zone - Overview L2-L3.pptxSAP Build Work Zone - Overview L2-L3.pptx
SAP Build Work Zone - Overview L2-L3.pptxNavinnSomaal
 
Artificial intelligence in cctv survelliance.pptx
Artificial intelligence in cctv survelliance.pptxArtificial intelligence in cctv survelliance.pptx
Artificial intelligence in cctv survelliance.pptxhariprasad279825
 
Gen AI in Business - Global Trends Report 2024.pdf
Gen AI in Business - Global Trends Report 2024.pdfGen AI in Business - Global Trends Report 2024.pdf
Gen AI in Business - Global Trends Report 2024.pdfAddepto
 
Anypoint Exchange: It’s Not Just a Repo!
Anypoint Exchange: It’s Not Just a Repo!Anypoint Exchange: It’s Not Just a Repo!
Anypoint Exchange: It’s Not Just a Repo!Manik S Magar
 
Ensuring Technical Readiness For Copilot in Microsoft 365
Ensuring Technical Readiness For Copilot in Microsoft 365Ensuring Technical Readiness For Copilot in Microsoft 365
Ensuring Technical Readiness For Copilot in Microsoft 3652toLead Limited
 
Designing IA for AI - Information Architecture Conference 2024
Designing IA for AI - Information Architecture Conference 2024Designing IA for AI - Information Architecture Conference 2024
Designing IA for AI - Information Architecture Conference 2024Enterprise Knowledge
 
Beyond Boundaries: Leveraging No-Code Solutions for Industry Innovation
Beyond Boundaries: Leveraging No-Code Solutions for Industry InnovationBeyond Boundaries: Leveraging No-Code Solutions for Industry Innovation
Beyond Boundaries: Leveraging No-Code Solutions for Industry InnovationSafe Software
 
SIP trunking in Janus @ Kamailio World 2024
SIP trunking in Janus @ Kamailio World 2024SIP trunking in Janus @ Kamailio World 2024
SIP trunking in Janus @ Kamailio World 2024Lorenzo Miniero
 
Story boards and shot lists for my a level piece
Story boards and shot lists for my a level pieceStory boards and shot lists for my a level piece
Story boards and shot lists for my a level piececharlottematthew16
 
Search Engine Optimization SEO PDF for 2024.pdf
Search Engine Optimization SEO PDF for 2024.pdfSearch Engine Optimization SEO PDF for 2024.pdf
Search Engine Optimization SEO PDF for 2024.pdfRankYa
 
Kotlin Multiplatform & Compose Multiplatform - Starter kit for pragmatics
Kotlin Multiplatform & Compose Multiplatform - Starter kit for pragmaticsKotlin Multiplatform & Compose Multiplatform - Starter kit for pragmatics
Kotlin Multiplatform & Compose Multiplatform - Starter kit for pragmaticscarlostorres15106
 
Nell’iperspazio con Rocket: il Framework Web di Rust!
Nell’iperspazio con Rocket: il Framework Web di Rust!Nell’iperspazio con Rocket: il Framework Web di Rust!
Nell’iperspazio con Rocket: il Framework Web di Rust!Commit University
 
CloudStudio User manual (basic edition):
CloudStudio User manual (basic edition):CloudStudio User manual (basic edition):
CloudStudio User manual (basic edition):comworks
 
Scanning the Internet for External Cloud Exposures via SSL Certs
Scanning the Internet for External Cloud Exposures via SSL CertsScanning the Internet for External Cloud Exposures via SSL Certs
Scanning the Internet for External Cloud Exposures via SSL CertsRizwan Syed
 

Último (20)

The Future of Software Development - Devin AI Innovative Approach.pdf
The Future of Software Development - Devin AI Innovative Approach.pdfThe Future of Software Development - Devin AI Innovative Approach.pdf
The Future of Software Development - Devin AI Innovative Approach.pdf
 
Unleash Your Potential - Namagunga Girls Coding Club
Unleash Your Potential - Namagunga Girls Coding ClubUnleash Your Potential - Namagunga Girls Coding Club
Unleash Your Potential - Namagunga Girls Coding Club
 
DevEX - reference for building teams, processes, and platforms
DevEX - reference for building teams, processes, and platformsDevEX - reference for building teams, processes, and platforms
DevEX - reference for building teams, processes, and platforms
 
DMCC Future of Trade Web3 - Special Edition
DMCC Future of Trade Web3 - Special EditionDMCC Future of Trade Web3 - Special Edition
DMCC Future of Trade Web3 - Special Edition
 
"Federated learning: out of reach no matter how close",Oleksandr Lapshyn
"Federated learning: out of reach no matter how close",Oleksandr Lapshyn"Federated learning: out of reach no matter how close",Oleksandr Lapshyn
"Federated learning: out of reach no matter how close",Oleksandr Lapshyn
 
What's New in Teams Calling, Meetings and Devices March 2024
What's New in Teams Calling, Meetings and Devices March 2024What's New in Teams Calling, Meetings and Devices March 2024
What's New in Teams Calling, Meetings and Devices March 2024
 
SAP Build Work Zone - Overview L2-L3.pptx
SAP Build Work Zone - Overview L2-L3.pptxSAP Build Work Zone - Overview L2-L3.pptx
SAP Build Work Zone - Overview L2-L3.pptx
 
Artificial intelligence in cctv survelliance.pptx
Artificial intelligence in cctv survelliance.pptxArtificial intelligence in cctv survelliance.pptx
Artificial intelligence in cctv survelliance.pptx
 
Gen AI in Business - Global Trends Report 2024.pdf
Gen AI in Business - Global Trends Report 2024.pdfGen AI in Business - Global Trends Report 2024.pdf
Gen AI in Business - Global Trends Report 2024.pdf
 
Anypoint Exchange: It’s Not Just a Repo!
Anypoint Exchange: It’s Not Just a Repo!Anypoint Exchange: It’s Not Just a Repo!
Anypoint Exchange: It’s Not Just a Repo!
 
Ensuring Technical Readiness For Copilot in Microsoft 365
Ensuring Technical Readiness For Copilot in Microsoft 365Ensuring Technical Readiness For Copilot in Microsoft 365
Ensuring Technical Readiness For Copilot in Microsoft 365
 
Designing IA for AI - Information Architecture Conference 2024
Designing IA for AI - Information Architecture Conference 2024Designing IA for AI - Information Architecture Conference 2024
Designing IA for AI - Information Architecture Conference 2024
 
Beyond Boundaries: Leveraging No-Code Solutions for Industry Innovation
Beyond Boundaries: Leveraging No-Code Solutions for Industry InnovationBeyond Boundaries: Leveraging No-Code Solutions for Industry Innovation
Beyond Boundaries: Leveraging No-Code Solutions for Industry Innovation
 
SIP trunking in Janus @ Kamailio World 2024
SIP trunking in Janus @ Kamailio World 2024SIP trunking in Janus @ Kamailio World 2024
SIP trunking in Janus @ Kamailio World 2024
 
Story boards and shot lists for my a level piece
Story boards and shot lists for my a level pieceStory boards and shot lists for my a level piece
Story boards and shot lists for my a level piece
 
Search Engine Optimization SEO PDF for 2024.pdf
Search Engine Optimization SEO PDF for 2024.pdfSearch Engine Optimization SEO PDF for 2024.pdf
Search Engine Optimization SEO PDF for 2024.pdf
 
Kotlin Multiplatform & Compose Multiplatform - Starter kit for pragmatics
Kotlin Multiplatform & Compose Multiplatform - Starter kit for pragmaticsKotlin Multiplatform & Compose Multiplatform - Starter kit for pragmatics
Kotlin Multiplatform & Compose Multiplatform - Starter kit for pragmatics
 
Nell’iperspazio con Rocket: il Framework Web di Rust!
Nell’iperspazio con Rocket: il Framework Web di Rust!Nell’iperspazio con Rocket: il Framework Web di Rust!
Nell’iperspazio con Rocket: il Framework Web di Rust!
 
CloudStudio User manual (basic edition):
CloudStudio User manual (basic edition):CloudStudio User manual (basic edition):
CloudStudio User manual (basic edition):
 
Scanning the Internet for External Cloud Exposures via SSL Certs
Scanning the Internet for External Cloud Exposures via SSL CertsScanning the Internet for External Cloud Exposures via SSL Certs
Scanning the Internet for External Cloud Exposures via SSL Certs
 

Cursus phpunit

  • 1. TRAINING COURSE PHPUNIT Training Course PHPUnit Training Course PHPUnit Nick Belhomme 2010 p. 1
  • 2. TRAINING COURSE PHPUNIT Goal of this course We will introduce you into the art of unit testing. What is Unit testing and how does it work. Together we will build a testing suite for a domain model (computer text based game) and finally implement this model into Zend Framework 1.11 and up. Making you comfortable with the PHPUnit testing framework. Training Course PHPUnit Nick Belhomme 2010 p. 2
  • 3. TRAINING COURSE PHPUNIT What where how? Training Course PHPUnit Nick Belhomme 2010 p. 3
  • 4. TRAINING COURSE PHPUNIT What is Unit Testing? Unit Testing is the testing of units. The tests are done to ensure that each unit is working like it is intended to work. The tests should be automatic and performed on regular intervals performing validation and verification on the correct working of the units tested. Ideally each test should be independent of another. A unit to be tested is a small piece of software code. Today in Object oriented programming languages these small pieces of software code are generally individual methods from a specific class. Unit testing gives us a way to test our implementations, design and behavior on the classes we write. Today we can claim that Unit testing is a fundamental part of quality modern software development. Benefits of Unit Testing Unit testing has three main benefits: • It makes you think about your application and about the implementation route you have chosen / are about to chose instead of running off half cocked implementing bad design decisions. • It gives an immediate view on the proper functioning of the system and facilitates locating a defect in the system. • It facilitates the refactoring of existing code. Which is equally important to guarantee the longevity of an application. "Refactoring is the process of changing a software system in such a way that it does not alter the external behavior of the code yet improves its internal structure. It is a disciplined way to clean up code that minimizes the chances of introducing bugs. [Preface Refactoring Martin Fowler]" When something breaks the application because of external factors (ie. DB credential changes) or because changes made to the code, Unit Tests will quickly show you were the error or bug has been introduced. Allowing you to quickly locate and fix the bug. In the end speeding up development time. Because of the tests in place you as a developer will feel more confident in your code and in your future changes. Reflecting that confidence to customers, management and other developers. Training Course PHPUnit Nick Belhomme 2010 p. 4
  • 5. TRAINING COURSE PHPUNIT Testing pieces of software Mostly for (PHP) developers testing their code was programming something and displaying it in the browser. If it worked we moved on to the next piece of software to implement. In a modern development cycle unit testing is a core component. Instead of testing stuff in the browser we also test by automating different scenarios. “In general if you think you need a var_dump() for testing your code, you are better of making that test eternally embedded in your test-suite. Martin-Fowler” Every programmer makes mistakes. What differentiates the good from the bad is that the good programmer uses tests to detect his mistakes ASAP. Reducing debugging costs to a minimum and making the product more stable. Okay up to our first test. We are going to test the CAST operator <?php $fixture = new stdClass(); $fixture->id = 1248; $fixture->name = 'Robin Hood'; // $fixture is expected to be an object from the type stdClass // with two properties set $fixture = (array) $fixture; // $fixture is expected to be an array // with two key value pairs set If we want to check if the behavior of the (array) cast operator is working as intended we can add a simple check <?php $fixture = new stdClass(); $fixture->id = 1248; $fixture->name = 'Robin Hood'; echo is_object($fixture), PHP_EOL; echo $fixture->id == 1248, PHP_EOL; echo $fixture->name == 'Robin Hood', PHP_EOL; $fixture = (array) $fixture; echo is_array($fixture),PHP_EOL; echo $fixture['id'] == 1248, PHP_EOL; echo $fixture['name']== 'Robin Hood', PHP_EOL; Training Course PHPUnit Nick Belhomme 2010 p. 5
  • 6. TRAINING COURSE PHPUNIT Everything is working as expected because we get for the 6 tests all 1 But at this time the tests are not automated and still require a human to check if every test has returned 1. Scanning over a big list of “ones” could get tiring and frustrating if you have to do it several times a day. We should automate this so that the tests only report unwanted behavior. Let's improve this. <?php $fixture = new stdClass(); $fixture->id = 1248; $fixture->name = 'Robin Hood'; assertTrue(is_object($fixture)); assertTrue($fixture->id == 1248); assertTrue($fixture->name == 'Robin Hood'); $fixture = (array) $fixture; assertTrue(is_array($fixture)); assertTrue($fixture['id'] == 1248); assertTrue($fixture['name']== 'Robin Hood'); function assertTrue($condition) { if (!$condition) { throw new Exception('Assertion failed.'); } } The tests are now fully automated and only report when something fails. There are several test suites readily available to make life easier for you as a developer. These test suites have the functions like assertTrue already implemented amongst many others. This course is on the topic on the XUnit family of testing frameworks and more specifically PHPUnit. Training Course PHPUnit Nick Belhomme 2010 p. 6
  • 7. TRAINING COURSE PHPUNIT PHPUnit as a part of the XUnit Family The XUnit family is a collective of various code-driven testing frameworks. They provide an automated solution with no need to write the same tests many times, and no need to remember what the result should be of each test. The name XUnit is a derivation of JUnit the first to be widely known. Each programming language has his own XUnit framework(s). The two most famous Unit testing frameworks for PHP are SimpleTest by Marcus Baker and PHPUnit by Sebastian Bergmann. In this course we have chosen to elaborate PHPUnit. The reason is because it is more widely adopted for PHP. Which is reflected by the adoption for it in major PHP frameworks like Zend Framework and Symfony. Another reason is SimpleTest is not really maintained anymore. When to create Unit tests When you have classes which aren't covered yet by tests you should start to setup a testing suite to make sure your code gets validated and checked on a regular interval. If you put those tests in place you will become more confident in adding extra functionality and refactoring your existing code base. You could follow this methodology. Writing first the classes and then the tests. While this is better than writing no tests at all it has some drawbacks: • Some tests will not get written. • Not that much thought will be put in the initial implementation. And putting initial thought into your implementation is a key principal for every good programmer. If we analyse the previous thought cycle we come to the conclusion that it is a wise decision to put first the tests in place and then the implementation. This is exactly what Test Driven Development TDD is all about. It dictates that all tests should be written prior to implementing the code. TDD is a derivation from Xtreme Programming and it makes much sense to develop this way. When we develop an application we should think about it before implementing it. TDD forces this path. And puts tests in place before even implementing a single piece of application code. But you shouldn't think in terms of tests but in terms of behavior. This brings us to Behavior Driven Development. Which has the benefits of TDD but shifts the focus. “So if it's not about testing, what's it about? It's about figuring out what you are trying to do before you run off half-cocked to try to do it. You write a specification that nails down a small aspect of behaviour in a concise, unambiguous, and executable form. It's that simple. Does that mean you write tests? No. It means you write specifications of what your code will have to do. It means you specify the behaviour of your code ahead of time. But not far ahead of time. In fact, just before you write the code is best because that's when you have as much information at hand as you will up to that point. Like well done TDD, Training Course PHPUnit Nick Belhomme 2010 p. 7
  • 8. TRAINING COURSE PHPUNIT you work in tiny increments... specifying one small aspect of behaviour at a time, then implementing it. When you realize that it's all about specifying behaviour and not writing tests, your point of view shifts. Suddenly the idea of having a Test class for each of your production classes is ridiculously limiting. And the thought of testing each of your methods with its own test method (in a 1-1 relationship) will be laughable. —Dave Astels . Training Course PHPUnit Nick Belhomme 2010 p. 8
  • 9. TRAINING COURSE PHPUNIT Getting Started Training Course PHPUnit Nick Belhomme 2010 p. 9
  • 10. TRAINING COURSE PHPUNIT Installing PHPUnit Installing PHPUnit is straight forward. You use the Pear Installer, set up your path (which should already be done for PEAR) and that's it. 1. If you have PEAR already installed skip to step 2. go to your phpPEAR directory and do: WINDOWS: php phar.require_hash=0 go-pear.phar register the PEAR path to your system environment: right click "this computer" => "properties" => "advanced" => "environment variables". In the field "system variables" select PATH, "edit" and add the full path to PEAR to the end of the list. *NIX: php go-pear.phar register the PEAR path to your system environment 2. update your local PEAR environment: pear channel-discover pear.phpunit.de 3. install PHPUnit pear install phpunit/PHPUnit Defining the project We are going to build a text based adventure game. The setup of the game will be much alike the old adventure games of Sierra and Lucas Arts but then purely text based. We will have a character (you) which can travel from location to location, picking up objects, manipulating the objects and talking to characters. So we will need: Grid : the gaming world holding all of the locations. We should be able to change position on the grid. Training Course PHPUnit Nick Belhomme 2010 p. 10
  • 11. TRAINING COURSE PHPUNIT Tile: a location on the grid. This can be a room or for instance a jungle. We should be able to perform various actions on it and store objects in it. Item: an item on which various actions can be added in order to manipulate it. Also possibility to talk to the item. ItemCombination: Defines which two items can be used together. Action: an action, manipulation. Inventory: a list of items. Conversation: A conversation which can be linked to an item. All this classes will have their own prefix Game_ which will serve as a name space. It is always good practice to define each project and library with a specific name space / prefix. This way same name clashes will most likely not occur. Training Course PHPUnit Nick Belhomme 2010 p. 11
  • 12. TRAINING COURSE PHPUNIT Writing your first test case: Implementing the Grid First requirement is we will have to create a class called Game_Grid. Let's define this in a unit test. Tests/PHPUnit/Library/Game/GridTest.php <?php require_once 'PHPUnit/Framework.php'; require_once '../../../../Library/Game/Grid.php'; class Game_GridTest extends PHPUnit_Framework_TestCase { public function testConstructor() { $grid = new Game_Grid(); } } running the test will result a FATAL ERROR! Training Course PHPUnit Nick Belhomme 2010 p. 12
  • 13. TRAINING COURSE PHPUNIT This is because we didn't create the Game/Grid.php file yet OR we haven't setup our include path correctly OR the file Grid.php doesn't include a class of the type Game_Grid. Which can't be auto loaded. Once we have fixed these possible reasons we do not receive the fatal error anymore. Instead we get OK (1 test, 0 assertions) Let us analyze the unit test code. First we include our PHPUnit framework and our class to test with require statements. All Unit test cases are in the [class name]Test format. You take the class name to test and append it with Test. The class extends PHPUnit_Framework_TestCase which will give you access to the full power of Training Course PHPUnit Nick Belhomme 2010 p. 13
  • 14. TRAINING COURSE PHPUNIT PHPUnit. This includes various template methods such as setUp() and tearDown(), assertion methods, mocking, etc. We let PHPUnit find every test by sticking to the naming format that every function starting with the string test, is a test. So every test method starts with test appended with a string of choice. It is best to give a descriptive method name explaining what you are testing. In example: public function testGetDescriptionToReturnFalseOnInitialisation() PHPUnit finds all the tests by reflection. It searches for all methods of the format test* and will execute them in the order defined. You should make sure every test is independent of another. And finally all tests can have multiple assertions. In our test we didn't include any assertions yet but they will grow rapidly. So what did we do to fix the bug? 1) We first create our application structure It is good practice to separate the tests and the actual library into two distinct folders. We create a sub folder in Tests because several testing suites can be available for an application, PHPUnit is one of them. The test case classes in the Tests/PHPUnit directory (will) mirror the package and class structure of the System Under Test (SUT). Probably the easiest way to compose a test suite is to keep all test case source files in a test directory. PHPUnit can automatically discover and run the tests by recursively traversing the Training Course PHPUnit Nick Belhomme 2010 p. 14
  • 15. TRAINING COURSE PHPUNIT test directory. 2) Then we start by creating our unit test We created our first test called GridTest.php with the above Unit test embedded. All test cases respect the following file naming convention: [class-name-to-test] +“Test.php” 3) We execute the command: "phpunit GridTest.php" in the folder where the test is located. This will result in a fatal error: require_once... 4) We add the file Grid.php in the Library directory. 5) Re-run the test again and we will see it fail once more: Class not found Training Course PHPUnit Nick Belhomme 2010 p. 15 If you get an error that your operating system doesn't recognize phpunit, you haven't set your execution rights or the path environment variables correct. After installing PHPUnit you should be able to execute the phpunit command from everywhere in the file system.
  • 16. TRAINING COURSE PHPUNIT 6) Add the class definition into the Grid.php file. Library/Game/Grid.php <?php class Game_Grid {} 7) run the test once more and see it pass. Bootstrapping PHPUnit test cases Because a lot of basic functions are repetitive and should be loaded for each test we could bootstrap it. In the bootstrap we can for instance set the include path, or an auto loader. Or any other directive that your application needs for running correctly. You can load a bootstrap file by passing it as a parameter PHPUnit --bootstrap [bootstrap file] [test case to run] in example: PHPUnit --bootstrap Bootstrap.php GridTest.php In our test suite we will be bootstrapping our tests. This will include a basic auto loader so we do not have to write all the includes all the time. When we run our application itself we will also auto load it. Everything will be auto loaded in the application. So why should our unit tests be any different. Create a file called Bootstrap.php (you can call it whatever you want but it is always a good idea to stick to naming conventions. Because PHPUnit regards it as a bootstrap, you might as well name it Training Course PHPUnit Nick Belhomme 2010 p. 16
  • 17. TRAINING COURSE PHPUNIT that way). In the bootstrap we make sure our PHPUnit framework is loaded and implement a simple auto loader. <?php require_once 'PHPUnit/Framework.php'; function __autoload($className) { $path = explode('_', $className); $root = ''; if ($path[0] == 'App') { $root = '../../../../Application/Library/'; array_shift($path); } else if ($path[0] == 'Game') { $root = '../../../../Library/Game/'; array_shift($path); } require_once $root.implode(DIRECTORY_SEPARATOR, $path).'.php'; } Now that we have the auto loader in place we can remove the require_once from our tests. Training Course PHPUnit Nick Belhomme 2010 p. 17
  • 18. TRAINING COURSE PHPUNIT Configure PHPUnit with a phpunit.xml configuration file You can pass a lot of optional parameters to PHPUnit. For a complete list run the command without any test to run, any parameter or with the parameter --help PHPUnit PHPUnit --help We can use a configuration file to automagically pass parameters to the phpunit command. You can define which bootstrap to load and where to find it. Which tests should be run etc. PHPUnit will by default search for a file called phpunit.xml in the directory you run the command from. Of course you can name it any other file name. But we suggest you sticking to that. If you need multiple configuration files you can name them whatever you want. In example: phpunit.xml for normal testing phpunitCruisecontrol.xml for a special cruisecontrol setup which is a part of the continuous integration methodology. Let's take a closer look at the configuration file we will setup: Training Course PHPUnit Nick Belhomme 2010 p. 18
  • 19. TRAINING COURSE PHPUNIT Tests/PHPUnit/Library/Game/phpunit.xml <?xml version="1.0" encoding="utf-8"?> <phpunit bootstrap="./Bootstrap.php"> <testsuite name="Game"> <directory>./</directory> </testsuite> <logging> <log type="coverage-html" target="/tmp/PHPUnit/Coverage/" charset="UTF-8" yui="true", highlight="false" lowUpperBound="35" highLowerBound="70" /> </logging> </phpunit> This will tell PHPUnit you want the Bootstrap.php loaded as a bootstrap and the entire test suite called Game Test Suite is in the current directory and subfolders. Alternatively instead of setting a <directory> you can also specifically set the order using the <file> tag. The order in which you define the <file> tag is the order in which the tests will be executed. After specifying these configurations PHPUnit can find all tests automatically and you do not have to write PHPUnit --bootstrap Bootstrap.php GridTest.php anymore. The following command will suffice: PHPUnit Training Course PHPUnit Nick Belhomme 2010 p. 19 You do not have to specify the XML declaration for PHPUnit to understand it. But as always it is best practice.
  • 20. TRAINING COURSE PHPUNIT Training Course PHPUnit Nick Belhomme 2010 p. 20
  • 21. TRAINING COURSE PHPUNIT Writing Assertions We want to define the grid as a square which can hold Tiles. The Grid should be dynamic in size, take actions and store a position indicating where you are on the map. Let's first start implementing the constructor. We want it to accept the number of vertical tiles and horizontal tiles as integers. Then we want to be able to get the grid size set. We define the requirements by means of a Test. <?php public function testConstructorNormalParamsWithGetGridSize() { $x = rand(1,100); $y = rand(1,100); $grid = new Game_Grid($x,$y); $gridSize = $grid->getGridSize(); $this->assertEquals($x, $gridSize['x']); $this->assertEquals($y, $gridSize['y']); $grid = new Game_Grid((string) $x, (string) $y); $gridSize = $grid->getGridSize(); $this->assertEquals($x, $gridSize['x']); $this->assertEquals($y, $gridSize['y']); } We run the test: PHPUnit As you can see there is a fatal error: call to undefined method. Lets implement these requirements into Game_Class Training Course PHPUnit Nick Belhomme 2010 p. 21
  • 22. TRAINING COURSE PHPUNIT <?php class Game_Grid { protected $_grid; public function __construct($gridSizeX, $gridSizeY) { for ($x = 0; $x < (int) $gridSizeX; $x++) { for ($y = 0; $y < (int) $gridSizeY; $y++) { $this->_grid[$x][$y] = null; } } } public function getGridSize() { return array('x' => count($this->_grid), 'y' => count($this->_grid[0])); } } Testing for Exceptions and PHP Errors Okay so far so good. But what happens if we pass negative values or no integers at all? Time to put it to a test and make sure how code can handle these kind of situations. This is one of the fundamental rules in unit testing: check boundaries or abnormal situations. By thinking of ways your application might be used and putting it thru the test, you develop more robust and safer software. It also makes you understand your code and the requirements better. 1. Test for boundary conditions. 2. Test for both success and failure. Training Course PHPUnit Nick Belhomme 2010 p. 22
  • 23. TRAINING COURSE PHPUNIT 3. Test for general functionality. So let us extend the testCase with new tests and some more assertions. public function testConstructZeroParamsX() { $this->setExpectedException('Exception'); $grid = new Game_Grid(0,3); } public function testConstructZeroParamsY() { $this->setExpectedException('Exception'); $grid = new Game_Grid(2,0); } public function testConstructNegativeParamsX() { $this->setExpectedException('Exception'); $grid = new Game_Grid(-2,3); } /** * @expectedException Exception */ public function testConstructNegativeParamsY() { $grid = new Game_Grid(2,-3); } Here we define the requirement to throw an Exception when a negative parameter is given. You can tell the testing framework to expect an exception in two ways. * By setting a DocBlock with @expectedException [exception to expect] * By calling the setExpectedException($exceptionToExpect) method on the framework Both work equally well but you should choose one. Remember consistency makes reading much more comprehensible. The test will show us that this is not yet thrown by our implementation Training Course PHPUnit Nick Belhomme 2010 p. 23
  • 24. TRAINING COURSE PHPUNIT This will force us to write the following implementation: public function __construct($gridSizeX, $gridSizeY) { if ($gridSizeX <= 0 || $gridSizeY <= 0) { throw new Exception('The size of the Grid cannot be negative or zero'); } for ($x = 0; $x < (int) $gridSizeX; $x++) { for ($y = 0; $y < (int) $gridSizeY; $y++) { $this->_grid[$x][$y] = null; } } } Training Course PHPUnit Nick Belhomme 2010 p. 24 You can also test on PHP warnings for instance when you do type hinting for parameter checking. PHP will throw a warning when you define a function which expects a certain type but you provide another type. You can test that functionality with: $this->setExpectedException('PHPUnit_Framework_Error');. This is useful when you want your application to force the user to use a specific type. You can check if you have put this check in place.
  • 25. TRAINING COURSE PHPUNIT This time the test will succeed and you can move on to the next assertion you can think of. There are a lot of assertions to choose from. A complete list can be fount here: http://www.phpunit.de/manual/current/en/api.html#api.assert Training Course PHPUnit Nick Belhomme 2010 p. 25
  • 26. TRAINING COURSE PHPUNIT Incomplete and skipped tests Generally when you are working on a new test case class, you might want to begin by writing empty test methods for your public api such as: public function testGetTileWhenNoneIsSet() { $this->markTestIncomplete( 'This test has not been implemented yet.' ); } public function testGetTileFromPostionWhenNoTileIsSet() { $this->markTestIncomplete( 'This is a custom message.' ); } public function testAddTile() { $this->markTestIncomplete( 'This test has not been implemented yet.' ); } public function testAddTileOutsideGrid() { $this->markTestIncomplete( 'This test has not been implemented yet.' ); } public function testIsOnGrid() { $this->markTestIncomplete( 'This test has not been implemented yet.' ); } public function testPosition() { $this->markTestIncomplete( 'This test has not been implemented yet.' ); } public function testSetPositionOutsideGrid() { $this->markTestIncomplete( 'This test has not been implemented yet.' ); } public function testDefaultGridActions() { $this->markTestIncomplete( 'This test has not been implemented yet.' ); } public function testAddActionAndGetActions() { $this->markTestIncomplete( 'This test has not been implemented yet.' ); } This practice will make sure you do not forget to test some of your public api methods. PHPUnit will tell you on each run you did not yet implement the required test. Training Course PHPUnit Nick Belhomme 2010 p. 26
  • 27. TRAINING COURSE PHPUNIT Having an empty tests will not suffice. public function testSomething() { } Empty tests have the problem that they are interpreted as a success by the PHPUnit framework. This misinterpretation leads to the test reports being useless -- you cannot see whether a test is actually successful or just not yet implemented. Calling $this->fail() in the unimplemented test method does not help either, since then the test will be interpreted as a failure. This would be just as wrong as interpreting an unimplemented test as a success. To indicate that a certain test should be skipped we can use the method $this->markTestSkipped( 'The MySQLi extension is not available.' ); This is useful when some precondition is not met. For instance a mySQLi extention is not available. The descriptive messages passed as a parameter can be any string you like. Training Course PHPUnit Nick Belhomme 2010 p. 27
  • 28. TRAINING COURSE PHPUNIT Test Doubles We are going to add Tiles to our Game_Grid. First we add tests for getting the Tile. public function testGetTileWhenNoneIsSet() { $grid = new Game_Grid(1,1); $this->assertNull($grid->getTile(0,0)); } public function testGetTileOutsideGrid() { $this->setExpectedException('Exception'); $grid = new Game_Grid(1,1); $this->assertNull($grid->getTile(3,0)); } When we have implemented our Game_Grid class to make the tests OK we can proceed to add Tiles. How can we test this when the Game_Tile class hasn't been written yet? For this reason amongst others we have Mocks at our disposal. They are very powerful. public function testAddTile() { $tile = $this->getMock('stdClass', null, array(), 'Game_Tile'); $grid = new Game_Grid(1,1); $this->assertType('Game_Grid', $grid->addTile($tile, 0, 0)); $this->assertType('Game_Tile', $grid->getTile(0,0)); } At the moment there is no public api yet implemented for Game_Tile so Game_Grid cannot be aware of it and thus not use it. Mocking from stdClass will not work if there is interaction with Game_Tile from within the method Game_Grid::addTile(). When such an implementation is needed we will need to create the Game_Tile class and implement the called methods, properties or whatever is needed. Then we can use the Game_Tile class as a blueprint like we did with stdClass. Making sure no real actions are performed when called upon. “Sometimes it is just plain hard to test the system under test (SUT) because it depends on other components that cannot be used in the test environment. This could be because they aren't available, they will not return the results needed for the test or because executing them would have undesirable side effects. In other cases, our test strategy requires us to have more control or visibility of the internal behavior of the SUT. When we are writing a test in which we cannot (or chose not to) use a real depended-on component (DOC), we can replace it with a Test Double. The Test Double doesn't have to behave exactly like the real DOC; it merely has to provide the same API as the real one so that the SUT thinks it is the real one! [Gerard Meszaros - Meszaros2007] “ Training Course PHPUnit Nick Belhomme 2010 p. 28
  • 29. TRAINING COURSE PHPUNIT $this->getMock() method of PHPUnit accepts several parameters. protected function getMock($originalClassName, $methods = array(), array $arguments = array(), $mockClassName = '', $callOriginalConstructor = TRUE, $callOriginalClone = TRUE, $callAutoload = TRUE) As you can see only the first parameter is mandatory all others are optional. The first parameter is the name of an existing class needed to be mocked. PHPUnit will replace the implementation from all the methods from this class with “return null;” unless otherwise stated in parameter two or by further configuration of the mock object returned. The only method that will remain unchanged by default is the constructor which can also be mocked by setting the fifth parameter to false. As we said parameter two gives you the possibility to specify only those methods for which you want PHPUnit to change the implementation. Only the methods whose names are in the array are replaced with a configurable test double. The behavior of the other methods is not changed. The third parameter may hold a parameter array that is passed to the original class' constructor. The fourth parameter can be used to specify a class name for the generated test double class. The fifth parameter can be used to disable the call to the original class' constructor. The sixth parameter can be used to disable the call to the original class' clone constructor The seventh parameter can be used to disable __autoload() during the generation of the test double class. After implementing the tile functionality into the grid, we also need to implement the actions that can be set. We want to be able to move from tile to tile on the grid. This means we have to be able to add and get actions from Game_Grid. The tricky part in this implementation is that Game_Action should be aware on which Game_Item, Game_Tile and or Game_Grid object it is acting it's magic on. This was defined in the project requirements. This implies that when adding an action to the grid the grid sets itself as a subject to the action. (see class diagram) Stubbing is the practice of replacing an object with a test double that (optionally) returns configured return values. You can use a stub to "replace a real component on which the SUT depends so that the test has a control point for the indirect inputs of the SUT. This allows the test to force the SUT down paths it might not otherwise execute". Our implementation of Game_Action will be an abstract one. We could make it a fully implemented class which accepts a name by a setter. But we do not want that, because we feel like every action in the game should have a real hardcoded implementation and not generated on the fly. Of course this can change in time. In example future implementations may come from a CMS tool and then we will need the setters. But for now we do not want a mix of real objects with objects generated on the Training Course PHPUnit Nick Belhomme 2010 p. 29
  • 30. TRAINING COURSE PHPUNIT fly. First let us build the tests for Game_Action. To test Game_Action we will use the PHPUnit method PHPUnit_Testframework_TestCase::getMockForAbstractClass(). protected function getMockForAbstractClass($originalClassName, array $arguments = array(), $mockClassName = '', $callOriginalConstructor = TRUE, $callOriginalClone = TRUE, $callAutoload = TRUE) Which will instantiate an abstract class with a test double. Making sure our abstract execute is implemented. Tests/PHPUnit/Library/Game/ActionTest.php <?php class Game_ActionTest extends PHPUnit_Framework_TestCase { public function setUp() { $this->_action = $this->getMockForAbstractClass('Game_Action'); } public function testConstructorWithParams() { $grid = $this->getMock('Game_Grid', null, array(1,1)); $inventory = $this->getMock('Game_Inventory', null, array()); $this->assertType('Game_Action', $this->getMockForAbstractClass('Game_Action', array($grid, $inventory))); $this->assertType('Game_Action', $this->getMockForAbstractClass('Game_Action', array($grid))); $this->assertType('Game_Action', $this->getMockForAbstractClass('Game_Action', array(null, $inventory))); $this->assertType('Game_Action', $this->getMockForAbstractClass('Game_Action', array(null, null))); } public function testConstructorWithParamsNotGrid() { $this->setExpectedException('PHPUnit_Framework_Error'); $this->assertType('Game_Action', $this->getMockForAbstractClass('Game_Action', array('blabla'))); } public function testConstructorWithParamsNotInventory() { $this->setExpectedException('PHPUnit_Framework_Error'); $this->assertType('Game_Action', $this->getMockForAbstractClass('Game_Action', array(null, 'blabla'))); } Training Course PHPUnit Nick Belhomme 2010 p. 30
  • 31. TRAINING COURSE PHPUNIT public function testSetItem() { require_once 'Item/Stub.php'; $item = new Game_Item_Stub(); $this->assertType('Game_Action', $this->_action->setSubject($item)); $tile = $this->getMock('Game_Tile'); $this->assertType('Game_Action', $this->_action->setSubject($tile)); $grid = $this->getMock('Game_Grid', null, array(1,1)); $this->assertType('Game_Action', $this->_action->setSubject($grid)); } public function testSetItemIncorrectParam() { $this->setExpectedException('Exception'); $this->assertType('Game_Action', $this->_action->setSubject('blabla')); } public function testGetName() { $this->assertEquals('action', $this->_action->getName()); } public function testGetSynonyms() { $this->assertType('array', $this->_action->getSynonyms()); } public function testSetGrid() { $grid = $this->getMock('Game_Grid', null, array(1,1)); $this->assertType('Game_Action', $this->_action->setGrid($grid)); } public function testSetGridWithFaultyParam() { $this->setExpectedException('PHPUnit_Framework_Error'); $this->_action->setGrid('wrong param'); } public function testSetPersonalInventory() { $inventory = $this->getMock('Game_Inventory'); $this->assertType('Game_Action', $this->_action->setPersonalInventory($inventory)); } public function testSetPersonalInventoryWithFaultyParam() { $this->setExpectedException('PHPUnit_Framework_Error'); $this->_action->setPersonalInventory('wrong param'); } } Training Course PHPUnit Nick Belhomme 2010 p. 31
  • 32. TRAINING COURSE PHPUNIT Library/Game/Action.php <?php abstract class Game_Action { protected $_subject; protected $_name = 'action'; protected $_synonyms = array(); protected $_grid; protected $_personalInventory; public function __construct(Game_Grid $grid = null, Game_Inventory $inventory = null) { $this->_grid = $grid; $this->_personalInventory = $inventory; $this->_init(); } public function setSubject($subject) { if (!($subject instanceOf Game_Item) && !($subject instanceOf Game_Tile) && !($subject instanceOf Game_Grid)) { throw new Exception('Subject passed should be of type Game_subject, Game_Tile, Game_Grid'); } $this->_subject = $subject; return $this; } public function getName() { return $this->_name; } public function getSynonyms() { return $this->_synonyms; } public function setGrid(Game_Grid $grid) { $this->_grid = $grid; return $this; } public function setPersonalInventory(Game_Inventory $inventory) { $this->_personalInventory = $inventory; return $this; } abstract public function execute(); protected function _getExecutedMessageSuccess() { Training Course PHPUnit Nick Belhomme 2010 p. 32
  • 33. TRAINING COURSE PHPUNIT echo 'action executed'; } protected function _getExecutedMessageFailed() { echo 'action failed'; } protected function _executeSuccess() { // template method } protected function _executeFailed() { // template method } protected function _init() { // template method } } Now that Game_Action is implemented and tested it is time to create a helper stub. Because all the objects depending on actions need an action name to differentiate between the different actions by use of array keys, we will create a stub that will extend Game_Action and implement a setter. Game_Grid is such an object. We want to give it four moving actions: GoNorth, goSouth, goWest, goEast. To do this we must be able to change the name of the action. The stub Game_Action_Stub will extend the Game_Action abstract class, set the default name and add a setter. Tests/PHPUnit/Library/Game/Action/Stub.php <?php class Game_Action_Stub extends Game_Action { public function execute() { return null; } // helper function for testing public function setName($name) { $this->_name = $name; } } Training Course PHPUnit Nick Belhomme 2010 p. 33
  • 34. TRAINING COURSE PHPUNIT After having Game_Action in place we can continue testing Game_Grid. public function testAddActionAndGetActions() { require_once './Action/Stub.php'; $action = $this->getMock('Game_Action_Stub'); $action->expects($this->once()) ->method('setGrid') ->with($this->isInstanceOf('Game_Grid')); $action->expects($this->once()) ->method('getName') ->will($this->returnValue('stubAction')); $grid = new Game_Grid(2,2); $actions = $grid->getActions(); $initialActionsCount = count($actions); $this->assertType('Game_Grid', $grid->addAction($action)); $actions = $grid->getActions(); $this->assertEquals($initialActionsCount+1, count($actions)); // Same name, it will overwrite the previous one set $action2 = $this->getMock('Game_Action'); $action2->expects($this->once()) ->method('getName') ->will($this->returnValue('stubAction')); $this->assertType('Game_Grid', $grid->addAction($action2)); $actions = $grid->getActions(); $this->assertEquals($initialActionsCount+1, count($actions)); } We define in the test: 1) there should be a method Game_Grid::addAction and it should make one call to the method setGrid on the passed Game_Action instance passing the Game_Grid as a param. 2) Secondly it also should make a call to Game_Action::getName() and this method will return the string 'stubAction'. Thus forcing Game_Grid to accept a string value. Which it will actually use as the key in the $actions array. 3) It also should have a fluent interface so the return value of addAction should be Game_Grid itself. 4) Adding a new action will resolve in one action registered. Adding a second with the same name will overwrite the previous one added. This requirement is tested by creating another action with the same name and adding it to the Grid. public function addAction(Game_Action $action) { $this->_actions[$action->getName()] = $action; $action->setGrid($this); return $this; } Training Course PHPUnit Nick Belhomme 2010 p. 34
  • 35. TRAINING COURSE PHPUNIT Training Course PHPUnit Nick Belhomme 2010 p. 35 You cannot test the call to Game_Action::setGrid if you use getMockForAbstractClass(). This method replaces the abstract methods by a default implementation, leaving the others unchanged. It will always return failure because it cannot register the call. Thus we use getMock() which needs a non abstract class. Luckily for us we already defined the helper stub class earlier.
  • 36. TRAINING COURSE PHPUNIT Dependencies Tests/PHPUnit/Library/Game/GridTest.php <?php class Game_GridTest extends PHPUnit_Framework_TestCase { public function testConstructorNormalParamsWithGetGridSize() { $x = rand(1,100); $y = rand(1,100); $grid = new Game_Grid($x,$y); $gridSize = $grid->getGridSize(); $this->assertEquals($x, $gridSize['x']); $this->assertEquals($y, $gridSize['y']); $grid = new Game_Grid((string) $x, (string) $y); $gridSize = $grid->getGridSize(); $this->assertEquals($x, $gridSize['x']); $this->assertEquals($y, $gridSize['y']); } public function constructorProviderBadParams() { return array( array(0, 0), array(0, 1), array(1, 0), array(-1, 1), array(1, -1), array(-1, -1), ); } /** * * @dataProvider constructorProviderBadParams */ public function testConstructNegativeOrZeroParam($paramX, $paramY) { $this->setExpectedException('Exception'); $grid = new Game_Grid($paramX, $paramY); } public function testGetTileWhenNoneIsSet() { $grid = new Game_Grid(1,1); $this->assertNull($grid->getTile(0,0)); } public function testGetTileFromPostionWhenNoTileIsSet() { $grid = new Game_Grid(1,1); $this->assertNull($grid->getTileFromPosition()); } public function testAddTile() Training Course PHPUnit Nick Belhomme 2010 p. 36
  • 37. TRAINING COURSE PHPUNIT { $tile = $this->getMock('Game_Tile', null, array(), 'Game_Tile_Mock', false); $grid = new Game_Grid(1,1); $this->assertType('Game_Grid', $grid->addTile($tile, 0, 0)); $this->assertType('Game_Tile_Mock', $grid->getTile(0,0)); } public function testAddTileOutsideGrid() { $this->setExpectedException('Exception'); $tile = $this->getMock('Game_Tile', null, array(), 'Game_Tile_Mock', false); $grid = new Game_Grid(1,1); $grid->addTile($tile, 2, 3); } public function testIsOnGrid() { $grid = new Game_Grid(1,1); $this->assertTrue($grid->isOnGrid(0,0)); $this->assertFalse($grid->isOnGrid(0,1)); } public function testPosition() { $x = rand(1,100); $y = rand(1,100); $grid = new Game_Grid($x,$y); $position = $grid->getPosition(); $this->assertEquals(0, $position['x']); $this->assertEquals(0, $position['y']); $newPositionX = rand(0,$x-1); $newPositionY = rand(0,$y-1); $grid->setPosition($newPositionX, $newPositionY); $position = $grid->getPosition(); $this->assertEquals($newPositionX, $position['x']); $this->assertEquals($newPositionY, $position['y']); } public function testSetPositionOutsideGrid() { $this->setExpectedException('Exception'); $grid = new Game_Grid(1,1); $grid->setPosition(3,2); } public function testDefaultGridActions() { $grid = new Game_Grid(2,2); $actions = $grid->getActions(); $this->assertEquals(4, count($actions)); $gameActionGoNorthFound = false; $gameActionGoEastFound = false; $gameActionGoSouthFound = false; Training Course PHPUnit Nick Belhomme 2010 p. 37
  • 38. TRAINING COURSE PHPUNIT $gameActionGoWestFound = false; foreach ($actions as $action) { if ($action instanceof Game_Action_Go_North) { $gameActionGoNorthFound = true; } if ($action instanceof Game_Action_Go_East) { $gameActionGoEastFound = true; } if ($action instanceof Game_Action_Go_South) { $gameActionGoSouthFound = true; } if ($action instanceof Game_Action_Go_West) { $gameActionGoWestFound = true; } } $this->assertTrue($gameActionGoNorthFound); $this->assertTrue($gameActionGoEastFound); $this->assertTrue($gameActionGoSouthFound); $this->assertTrue($gameActionGoWestFound); return $grid; } /** * this dependency has been added to show this functionality * @depends testDefaultGridActions */ public function testAddActionAndGetActions(Game_Grid $grid) { require_once './Action/Stub.php'; $action = $this->getMock('Game_Action_Stub'); $action->expects($this->once()) ->method('setGrid') ->with($this->isInstanceOf('Game_Grid')); $action->expects($this->once()) ->method('getName') ->will($this->returnValue('stubAction')); $actions = $grid->getActions(); $this->assertEquals(4, count($actions)); $this->assertType('Game_Grid', $grid->addAction($action)); $actions = $grid->getActions(); $this->assertEquals(5, count($actions)); // Same name, it will overwrite the previous one set $action2 = $this->getMock('Game_Action'); $action2->expects($this->once()) ->method('getName') ->will($this->returnValue('stubAction')); $this->assertType('Game_Grid', $grid->addAction($action2)); $actions = $grid->getActions(); $this->assertEquals(5, count($actions)); } } Normally every test should be independent from each other. And should be able to be executed in random order. Yet sometimes to quickly localize defects, we want our attention to be focused on Training Course PHPUnit Nick Belhomme 2010 p. 38
  • 39. TRAINING COURSE PHPUNIT relevant failing tests. This is why PHPUnit skips the execution of a test when a depended-upon test has failed. This improves defect localization by exploiting the dependencies between tests as shown in the above testcase, “Exploiting the dependencies between tests”. Purely functioning as an example we have changed some tests in the above code. The test testAddActionAndGetActions depends on testDefaultGridActions. Because if the defaultGridActions are not set correctly then our assertEquals for adding and getting will also fail for testAddActionAndGetActions. We can define such a dependency by using a doc block annotation @depends [test on which the test is dependant]. We have the possibility to pass fixtures as params in such cases, making sure that the test environment is correctly set. For the above dependency, testDefaultGridActions is called the producer and testAddActionAndGetActions is called the consumer. The DataProvider Sometimes we want to test the same method with different params which should all result in the an outcome predicted by the test. To facilitate this a dataProvider has been made available. In the code in the previous chapter we have rewritten the tests from the chapter: “Testing for Exceptions and PHP Errors” to use the dataProvider annotation. This will make our testing much more easy to understand and extend. Because it removes redundancy. For each array that is part of the dataProvider collection the test method will be called with the contents of the array as its arguments. You create a public method which returns an array or an object which implements the Iterator interface. This method is called the dataProvider and you link it to the testmethod with a doc block @dataProvider [provider method] Training Course PHPUnit Nick Belhomme 2010 p. 39 A producer is a test method that yields its unit under test as return value. A consumer is a test method that depends on one or more producers and their return values. PHPUnit does not change the order in which tests are executed, you have to ensure that the dependencies of a test can actually be met before the test is run.
  • 40. TRAINING COURSE PHPUNIT Training Course PHPUnit Nick Belhomme 2010 p. 40 When a test receives input from both a @dataProvider method and from one or more tests it @depends on, the arguments from the data provider will come before the ones from depended-upon tests.
  • 41. TRAINING COURSE PHPUNIT setUp, tearDown and other template methods One of the most time-consuming parts of writing tests is writing the code to setup the world to a known state and then return it to its original state when the test is complete. This known state is called the fixture of the test. Setting and destructing a fixture for every test can be time consuming or a lot of copy and pasting, the latter being against good coding practice. DRY (Do Not Repeat yourself) is something every programmer should try to adhere to. PHPUnit has made it easy for us to to provide us with hooks in the form of template methods. Template methods are empty methods which are called in a predefined algorithm set by the class which owns the template methods. You may give the algorithm a specific implementation by implementing the methods. Template methods available in order of execution. • setUpBeforeClass(): Each time when your testcase class is run this method will be called once. Here you can create for example a file with content that will not change in the life cycle of the tests. And there for is not needed to be created every time between tests. • setUp(): Before a test method is run, a template method called setUp() is invoked. setUp() is where you create the objects against which you will test. • assertPreConditions(): After each setup but before each test, this method is called. Here you can implement a check on certain aspects of your testing stage. [THE ACTUAL TEST WILL BE EXECUTED HERE] • assertPostConditions(): After each test executed, this method is called. Here you can implement a check on certain aspects of your testing stage. • tearDown(): Once the test method has finished running, whether it succeeded or failed, another template method called tearDown() is invoked. tearDown() is where you clean allocated external resources like files or sockets. Also clean up objects for garbage collection. • tearDownAfterClass(): Each time when your testcase class has finished running all tests and template methods this last method will be called. This method will be called once. Here you can delete for example the file that was set in the setupBeforeClass(). Training Course PHPUnit Nick Belhomme 2010 p. 41
  • 42. TRAINING COURSE PHPUNIT Testing your tests, code coverage After creating a test for some functionality you should test the testcase for code coverage. What use do tests have if they do not cover all of your application code. They will provide you with a false sense of safety. Thus it is very important to tests your tests. Luckily for us, PHPUnit has such a feature embedded into it's service. It uses the Xdebug extension. PHPUnit can output code coverage in the following formats: • html: coverage report in HTML format phpunit --coverage-html <dir> • clover: code coverage data in Clover XML format phpunit --coverage-clover <file> • source: code coverage / source data in XML format phpunit --coverage-source <dir> To produce a code coverage test for ActionTest use the following command. phpunit --coverage-html /tmp/PHPUnit/Coverage/ ActionTest We have already defined the setup for the code coverage in our configuration file from chapter “Configure PHPUnit with a phpunit.xml configuration file” <logging> <log type="coverage-html" target="/tmp/PHPUnit/Coverage/" charset="UTF-8" yui="true", highlight="false" lowUpperBound="35" highLowerBound="70" /> </logging> The <logging> element and its <log> children can be used to configure the logging of the test execution. type: specifies the type of logging target: the target directory where the log should be written to charset: the character encoding for the log yui: Yahoo User Interface, true enables javascript on click events. Highlight: activates the code syntax highlighting. lowUpperBound: overwrite the default percentage of 35 to your upper limit of max coverage percentage that is needed to qualiy as low. HighLowerBound: overwrite the default percentage of 70 to your minimum coverage percentage is needed to qualify as high coverage. Training Course PHPUnit Nick Belhomme 2010 p. 42
  • 43. TRAINING COURSE PHPUNIT Increase the readability by creating smaller tests and adding assertion messages. Want to know more? Want to learn about all the nitty gritty hidden features of PHPUnit? Want to integrate PHPUnit in your IDE? Want to integrate PHPUnit in your ZF projects? Want to fully automate the running of tests with continuous integration? GOOD NEWS, YOU CAN! • I give workshops at community events for free (technically they are sponsored) • I can give a workshop at the company for which you work. (Your boss pays and during office hours you get to become a PHPUnit expert, pretty neat I would say) • Purchase my upcoming book on an important framework. THANK YOU FOR READING, Nick Belhomme follow me on twitter: @NickBelhomme http://nickbelhomme.com/phpunit-training Training Course PHPUnit Nick Belhomme 2010 p. 43