Training Course PHPUnit

Training Course PHPUnit

Nick Belhomme 2010

p. 1


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


What where how?

Training Course PHPUnit

Nick Belhomme 2010

p. 3


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

If it worked we moved on to the next piece of software to implement. $fixture->id = 1248. What differentiates the good from the bad is that the good programmer uses tests to detect his mistakes ASAP. $fixture->id = 1248. PHP_EOL. PHP_EOL. you are better of making that test eternally embedded in your test-suite. $fixture->name = 'Robin Hood'. Reducing debugging costs to a minimum and making the product more stable. Instead of testing stuff in the browser we also test by automating different scenarios. // $fixture is expected to be an object from the type stdClass // with two properties set $fixture = (array) $fixture. “In general if you think you need a var_dump() for testing your code. $fixture->name = 'Robin Hood'. PHP_EOL. echo $fixture['id'] == 1248. // $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(). 5 . In a modern development cycle unit testing is a core component. We are going to test the CAST operator <?php $fixture = new stdClass(). echo $fixture['name']== 'Robin Hood'. Okay up to our first test. PHP_EOL.TRAINING COURSE PHPUNIT Testing pieces of software Mostly for (PHP) developers testing their code was programming something and displaying it in the browser. Martin-Fowler” Every programmer makes mistakes. echo $fixture->id == 1248. echo is_object($fixture). echo $fixture->name == 'Robin Hood'. PHP_EOL.PHP_EOL. $fixture = (array) $fixture. Training Course PHPUnit Nick Belhomme 2010 p. echo is_array($fixture).

Scanning over a big list of “ones” could get tiring and frustrating if you have to do it several times a day. Let's improve this.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. There are several test suites readily available to make life easier for you as a developer. assertTrue(is_array($fixture)). assertTrue($fixture->name == 'Robin Hood'). Training Course PHPUnit Nick Belhomme 2010 p. $fixture = (array) $fixture. assertTrue($fixture['id'] == 1248). $fixture->name = 'Robin Hood'. function assertTrue($condition) { if (!$condition) { throw new Exception('Assertion failed. assertTrue(is_object($fixture)). We should automate this so that the tests only report unwanted behavior. <?php $fixture = new stdClass(). 6 . $fixture->id = 1248. assertTrue($fixture['name']== 'Robin Hood'). } } The tests are now fully automated and only report when something fails.'). 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. assertTrue($fixture->id == 1248).

Which has the benefits of TDD but shifts the focus. 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. TDD is a derivation from Xtreme Programming and it makes much sense to develop this way. TDD forces this path. This is exactly what Test Driven Development TDD is all about. Does that mean you write tests? No. The reason is because it is more widely adopted for PHP. It dictates that all tests should be written prior to implementing the code. It means you specify the behaviour of your code ahead of time. You write a specification that nails down a small aspect of behaviour in a concise. But not far ahead of time. 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. This brings us to Behavior Driven Development. 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. Writing first the classes and then the tests. In fact. While this is better than writing no tests at all it has some drawbacks: • Some tests will not get written. “So if it's not about testing. The two most famous Unit testing frameworks for PHP are SimpleTest by Marcus Baker and PHPUnit by Sebastian Bergmann. Training Course PHPUnit Nick Belhomme 2010 p. When we develop an application we should think about it before implementing it. unambiguous. If you put those tests in place you will become more confident in adding extra functionality and refactoring your existing code base. It means you write specifications of what your code will have to do. And puts tests in place before even implementing a single piece of application code. And putting initial thought into your implementation is a key principal for every good programmer. You could follow this methodology. Which is reflected by the adoption for it in major PHP frameworks like Zend Framework and Symfony. 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. 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. Like well done TDD. The name XUnit is a derivation of JUnit the first to be widely known. Each programming language has his own XUnit framework(s). But you shouldn't think in terms of tests but in terms of behavior. Another reason is SimpleTest is not really maintained anymore. 7 . It's that simple.TRAINING COURSE PHPUNIT PHPUnit as a part of the XUnit Family The XUnit family is a collective of various code-driven testing frameworks. and executable form. • Not that much thought will be put in the initial implementation. In this course we have chosen to elaborate PHPUnit.

specifying one small aspect of behaviour at a time.TRAINING COURSE PHPUNIT you work in tiny increments. —Dave Astels . then implementing it.. Suddenly the idea of having a Test class for each of your production classes is ridiculously limiting. Training Course PHPUnit Nick Belhomme 2010 p. 8 . And the thought of testing each of your methods with its own test method (in a 1-1 relationship) will be laughable. your point of view shifts. When you realize that it's all about specifying behaviour and not writing tests..

9 .TRAINING COURSE PHPUNIT Getting Started Training Course PHPUnit Nick Belhomme 2010 p.

If you have PEAR already installed skip to step 2. You use the Pear Installer. 10 . Training Course PHPUnit Nick Belhomme 2010 p. "edit" and add the full path to PEAR to the end of the list.require_hash=0 go-pear. In the field "system variables" select PATH. So we will need: Grid : the gaming world holding all of the locations. *NIX: php go-pear. go to your php\PEAR directory and do: WINDOWS: php phar. We should be able to change position on the grid. We will have a character (you) which can travel from location to location.phar register the PEAR path to your system environment: right click "this computer" => "properties" => "advanced" => "environment variables".de 3.phar register the PEAR path to your system environment 2. 1.TRAINING COURSE PHPUNIT Installing PHPUnit Installing PHPUnit is straight forward. picking up objects. The setup of the game will be much alike the old adventure games of Sierra and Lucas Arts but then purely text based. manipulating the objects and talking to characters. update your local PEAR environment: pear channel-discover pear. set up your path (which should already be done for PEAR) and that's it. install PHPUnit pear install phpunit/PHPUnit Defining the project We are going to build a text based adventure game.phpunit.

Item: an item on which various actions can be added in order to manipulate it. It is always good practice to define each project and library with a specific name space / prefix. Conversation: A conversation which can be linked to an item. Also possibility to talk to the item. All this classes will have their own prefix Game_ which will serve as a name space. manipulation. We should be able to perform various actions on it and store objects in it. an action. This can be a room or for instance a jungle. a list of items. This way same name clashes will most likely not occur. ItemCombination: Action: Inventory: Defines which two items can be used together. Training Course PHPUnit Nick Belhomme 2010 p.TRAINING COURSE PHPUNIT Tile: a location on the grid. 11 .

./././Library/Game/Grid.. Let's define this in a unit test. class Game_GridTest extends PHPUnit_Framework_TestCase { public function testConstructor() { $grid = new Game_Grid().TRAINING COURSE PHPUNIT Writing your first test case: Implementing the Grid First requirement is we will have to create a class called Game_Grid. require_once './.. Tests/PHPUnit/Library/Game/GridTest. } } running the test will result a FATAL ERROR! Training Course PHPUnit Nick Belhomme 2010 p..php'.php'. 12 .php <?php require_once 'PHPUnit/Framework.

Once we have fixed these possible reasons we do not receive the fatal error anymore. You take the class name to test and append it with Test. 13 .php doesn't include a class of the type Game_Grid. Which can't be auto loaded. First we include our PHPUnit framework and our class to test with require statements.TRAINING COURSE PHPUNIT This is because we didn't create the Game/Grid. Instead we get OK (1 test.php file yet OR we haven't setup our include path correctly OR the file Grid. All Unit test cases are in the [class name]Test format. 0 assertions) Let us analyze the unit test code. The class extends PHPUnit_Framework_TestCase which will give you access to the full power of Training Course PHPUnit Nick Belhomme 2010 p.

This includes various template methods such as setUp() and tearDown(). 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. is a test.TRAINING COURSE PHPUNIT PHPUnit. And finally all tests can have multiple assertions. It searches for all methods of the format test* and will execute them in the order defined. assertion methods. PHPUnit is one of them. In example: public function testGetDescriptionToReturnFalseOnInitialisation() PHPUnit finds all the tests by reflection. You should make sure every test is independent of another. 14 . mocking. etc. PHPUnit can automatically discover and run the tests by recursively traversing the Training Course PHPUnit Nick Belhomme 2010 p. So every test method starts with test appended with a string of choice. We create a sub folder in Tests because several testing suites can be available for an application. 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 let PHPUnit find every test by sticking to the naming format that every function starting with the string test. It is best to give a descriptive method name explaining what you are testing.

All test cases respect the following file naming convention: [class-name-to-test] + “Test... 15 . 5) Re-run the test again and we will see it fail once more: Class not found Training Course PHPUnit Nick Belhomme 2010 p. After installing PHPUnit you should be able to execute the phpunit command from everywhere in the file system. This will result in a fatal error: require_once. If you get an error that your operating system doesn't recognize phpunit.php" in the folder where the test is located.php” 3) We execute the command: "phpunit GridTest.php with the above Unit test embedded.TRAINING COURSE PHPUNIT test directory. 4) We add the file Grid. 2) Then we start by creating our unit test We created our first test called GridTest.php in the Library directory. you haven't set your execution rights or the path environment variables correct.

php <?php class Game_Grid {} 7) run the test once more and see it pass.php (you can call it whatever you want but it is always a good idea to stick to naming conventions. or an auto loader. Bootstrapping PHPUnit test cases Because a lot of basic functions are repetitive and should be loaded for each test we could bootstrap it. Everything will be auto loaded in the application. This will include a basic auto loader so we do not have to write all the includes all the time.php In our test suite we will be bootstrapping our tests. you might as well name it Training Course PHPUnit Nick Belhomme 2010 p.php GridTest. Create a file called Bootstrap.php file. Library/Game/Grid. 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. Or any other directive that your application needs for running correctly. When we run our application itself we will also auto load it. 16 .TRAINING COURSE PHPUNIT 6) Add the class definition into the Grid. In the bootstrap we can for instance set the include path. So why should our unit tests be any different. Because PHPUnit regards it as a bootstrap.

/. if ($path[0] == 'App') { $root = '.. $root = ''.'.php'. array_shift($path).. $className)./Library/Game/'. Training Course PHPUnit Nick Belhomme 2010 p.implode(DIRECTORY_SEPARATOR./../.. } Now that we have the auto loader in place we can remove the require_once from our tests.. 17 .. array_shift($path).. $path)./. In the bootstrap we make sure our PHPUnit framework is loaded and implement a simple auto loader. } else if ($path[0] == 'Game') { $root = '.TRAINING COURSE PHPUNIT that way)././. } require_once $root.php'. function __autoload($className) { $path = explode('_'. <?php require_once 'PHPUnit/Framework./Application/Library/'..

For a complete list run the command without any test to run. In example: phpunit. any parameter or with the parameter --help PHPUnit PHPUnit --help We can use a configuration file to automagically pass parameters to the phpunit command. PHPUnit will by default search for a file called phpunit. Of course you can name it any other file name. But we suggest you sticking to that. You can define which bootstrap to load and where to find it.TRAINING COURSE PHPUNIT Configure PHPUnit with a phpunit. Which tests should be run etc.xml in the directory you run the command from. If you need multiple configuration files you can name them whatever you want.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 .xml configuration file You can pass a lot of optional parameters to PHPUnit.xml for normal testing phpunitCruisecontrol.

php anymore. But as always it is best practice. The following command will suffice: PHPUnit Training Course PHPUnit Nick Belhomme 2010 p.xml <?xml version="1. highlight="false" lowUpperBound="35" highLowerBound="70" /> </logging> </phpunit> This will tell PHPUnit you want the Bootstrap. After specifying these configurations PHPUnit can find all tests automatically and you do not have to write PHPUnit --bootstrap Bootstrap.TRAINING COURSE PHPUNIT Tests/PHPUnit/Library/Game/phpunit./Bootstrap. The order in which you define the <file> tag is the order in which the tests will be executed.php loaded as a bootstrap and the entire test suite called Game Test Suite is in the current directory and subfolders./</directory> </testsuite> <logging> <log type="coverage-html" target="/tmp/PHPUnit/Coverage/" charset="UTF-8" yui="true".php GridTest. You do not have to specify the XML declaration for PHPUnit to understand it.0" encoding="utf-8"?> <phpunit bootstrap=". 19 .php"> <testsuite name="Game"> <directory>. Alternatively instead of setting a <directory> you can also specifically set the order using the <file> tag.

TRAINING COURSE PHPUNIT Training Course PHPUnit Nick Belhomme 2010 p. 20 .

We want it to accept the number of vertical tiles and horizontal tiles as integers. $this->assertEquals($x. The Grid should be dynamic in size. <?php public function testConstructorNormalParamsWithGetGridSize() { $x = rand(1. We define the requirements by means of a Test. $this->assertEquals($x. $grid = new Game_Grid($x. $y = rand(1. $grid = new Game_Grid((string) $x. $gridSize = $grid->getGridSize(). Then we want to be able to get the grid size set. $gridSize = $grid->getGridSize().$y). $gridSize['x']). Lets implement these requirements into Game_Class Training Course PHPUnit Nick Belhomme 2010 p.100). 21 . (string) $y). $this->assertEquals($y. take actions and store a position indicating where you are on the map. $gridSize['y']). } We run the test: PHPUnit As you can see there is a fatal error: call to undefined method.100). Let's first start implementing the constructor. $gridSize['x']). $this->assertEquals($y. $gridSize['y']).TRAINING COURSE PHPUNIT Writing Assertions We want to define the grid as a square which can hold Tiles.

public function __construct($gridSizeX. By thinking of ways your application might be used and putting it thru the test. 'y' => count($this->_grid[0])). $x++) { for ($y = 0. $gridSizeY) { for ($x = 0. 22 Training Course PHPUnit . 2. you develop more robust and safer software. This is one of the fundamental rules in unit testing: check boundaries or abnormal situations. It also makes you understand your code and the requirements better. } } Testing for Exceptions and PHP Errors Okay so far so good. Nick Belhomme 2010 p. 1. $x < (int) $gridSizeX.TRAINING COURSE PHPUNIT <?php class Game_Grid { protected $_grid. $y < (int) $gridSizeY. } } } public function getGridSize() { return array('x' => count($this->_grid). $y++) { $this->_grid[$x][$y] = null. Test for both success and failure. Test for boundary conditions. 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.

3). So let us extend the testCase with new tests and some more assertions. $grid = new Game_Grid(0. * 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. public function testConstructZeroParamsX() { $this->setExpectedException('Exception').0). Test for general functionality. Remember consistency makes reading much more comprehensible. You can tell the testing framework to expect an exception in two ways.TRAINING COURSE PHPUNIT 3. } Here we define the requirement to throw an Exception when a negative parameter is given. $grid = new Game_Grid(2. } /** * @expectedException Exception */ public function testConstructNegativeParamsY() { $grid = new Game_Grid(2. } public function testConstructNegativeParamsX() { $this->setExpectedException('Exception'). $grid = new Game_Grid(-2. The test will show us that this is not yet thrown by our implementation Training Course PHPUnit Nick Belhomme 2010 p. 23 .3).-3). } public function testConstructZeroParamsY() { $this->setExpectedException('Exception').

$y++) { $this->_grid[$x][$y] = null. This will force us to write the following implementation: public function __construct($gridSizeX. $x < (int) $gridSizeX. This is useful when you want your application to force the user to use a specific type. } for ($x = 0.TRAINING COURSE PHPUNIT You can also test on PHP warnings for instance when you do type hinting for parameter checking. } } } Training Course PHPUnit Nick Belhomme 2010 p. $y < (int) $gridSizeY. $x++) { for ($y = 0. 24 . You can test that functionality with: $this->setExpectedException('PHPUnit_Framework_Error').. $gridSizeY) { if ($gridSizeX <= 0 || $gridSizeY <= 0) { throw new Exception('The size of the Grid cannot be negative or zero'). You can check if you have put this check in place. PHP will throw a warning when you define a function which expects a certain type but you provide another type.

TRAINING COURSE PHPUNIT This time the test will succeed and you can move on to the next assertion you can think of.html#api. 25 .assert Training Course PHPUnit Nick Belhomme 2010 p.phpunit. A complete list can be fount here: http://www. There are a lot of assertions to choose

' ). 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. Training Course PHPUnit Nick Belhomme 2010 p.' ). } public function testIsOnGrid() { $this->markTestIncomplete( 'This test has not been implemented yet.TRAINING COURSE PHPUNIT Incomplete and skipped tests Generally when you are working on a new test case class. } public function testAddTile() { $this->markTestIncomplete( 'This test has not been implemented yet.' ). PHPUnit will tell you on each run you did not yet implement the required test.' ). } public function testDefaultGridActions() { $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.' ). } public function testGetTileFromPostionWhenNoTileIsSet() { $this->markTestIncomplete( 'This is a custom message. } public function testPosition() { $this->markTestIncomplete( 'This test has not been implemented yet. 26 .' ). } public function testAddTileOutsideGrid() { $this->markTestIncomplete( 'This test has not been implemented yet. } public function testAddActionAndGetActions() { $this->markTestIncomplete( 'This test has not been implemented yet.' ).' ). } public function testSetPositionOutsideGrid() { $this->markTestIncomplete( 'This test has not been implemented yet.' ).

public function testSomething() { } Empty tests have the problem that they are interpreted as a success by the PHPUnit framework. For instance a mySQLi extention is not available. Calling $this->fail() in the unimplemented test method does not help either.' ). The descriptive messages passed as a parameter can be any string you like. This is useful when some precondition is not met. Training Course PHPUnit Nick Belhomme 2010 p.TRAINING COURSE PHPUNIT Having an empty tests will not suffice. To indicate that a certain test should be skipped we can use the method $this->markTestSkipped( 'The MySQLi extension is not available. This misinterpretation leads to the test reports being useless cannot see whether a test is actually successful or just not yet implemented. This would be just as wrong as interpreting an unimplemented test as a success. 27 . since then the test will be interpreted as a failure.

First we add tests for getting the Tile. The Test Double doesn't have to behave exactly like the real DOC. When such an implementation is needed we will need to create the Game_Tile class and implement the called methods. 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.1). } 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. “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.0)).1). $grid = new Game_Grid(1. $this->assertType('Game_Grid'. Making sure no real actions are performed when called upon. they will not return the results needed for the test or because executing them would have undesirable side effects. When we are writing a test in which we cannot (or chose not to) use a real depended-on component (DOC).Meszaros2007] “ Training Course PHPUnit Nick Belhomme 2010 p. properties or whatever is needed. $this->assertType('Game_Tile'. $grid->addTile($tile. it merely has to provide the same API as the real one so that the SUT thinks it is the real one! [Gerard Meszaros . array(). 0)). $grid = new Game_Grid(1. } When we have implemented our Game_Grid class to make the tests OK we can proceed to add Tiles. our test strategy requires us to have more control or visibility of the internal behavior of the SUT. 0. 'Game_Tile'). $grid->getTile(0. In other cases. Mocking from stdClass will not work if there is interaction with Game_Tile from within the method Game_Grid::addTile(). Then we can use the Game_Tile class as a blueprint like we did with stdClass.1). $this->assertNull($grid->getTile(0. public function testAddTile() { $tile = $this->getMock('stdClass'. } public function testGetTileOutsideGrid() { $this->setExpectedException('Exception'). They are very powerful. This could be because they aren't available.0)). public function testGetTileWhenNoneIsSet() { $grid = new Game_Grid(1.TRAINING COURSE PHPUNIT Test Doubles We are going to add Tiles to our Game_Grid.0)). 28 . null. we can replace it with a Test Double. $this->assertNull($grid->getTile(3.

We want to be able to move from tile to tile on the grid. array $arguments = array(). PHPUnit will replace the implementation from all the methods from this class with “return null. But for now we do not want a mix of real objects with objects generated on the Training Course PHPUnit Nick Belhomme 2010 p. $methods = array(). $callAutoload = TRUE) As you can see only the first parameter is mandatory all others are optional. Only the methods whose names are in the array are replaced with a configurable test double. we also need to implement the actions that can be set. $mockClassName = ''. Our implementation of Game_Action will be an abstract one. The third parameter may hold a parameter array that is passed to the original class' constructor. protected function getMock($originalClassName. (see class diagram) Stubbing is the practice of replacing an object with a test double that (optionally) returns configured return values. $callOriginalClone = TRUE. In example future implementations may come from a CMS tool and then we will need the setters. Of course this can change in time. 29 . This implies that when adding an action to the grid the grid sets itself as a subject to the action. 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. 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. because we feel like every action in the game should have a real hardcoded implementation and not generated on the fly. The behavior of the other methods is not changed. This means we have to be able to add and get actions from Game_Grid. The fifth parameter can be used to disable the call to the original class' constructor. $callOriginalConstructor = TRUE. As we said parameter two gives you the possibility to specify only those methods for which you want PHPUnit to change the implementation. After implementing the tile functionality into the grid. This allows the test to force the SUT down paths it might not otherwise execute". The only method that will remain unchanged by default is the constructor which can also be mocked by setting the fifth parameter to false. We could make it a fully implemented class which accepts a name by a setter. The first parameter is the name of an existing class needed to be mocked. The fourth parameter can be used to specify a class name for the generated test double class.” unless otherwise stated in parameter two or by further configuration of the mock object returned. Game_Tile and or Game_Grid object it is acting it's magic on. But we do not want that. This was defined in the project requirements.TRAINING COURSE PHPUNIT $this->getMock() method of PHPUnit accepts several parameters. The tricky part in this implementation is that Game_Action should be aware on which Game_Item.

$this->assertType('Game_Action'. array $arguments = array(). Tests/PHPUnit/Library/Game/ActionTest. $this->getMockForAbstractClass('Game_Action'. $callOriginalConstructor = TRUE. array(null. null))). array(null. $inventory = $this->getMock('Game_Inventory'. To test Game_Action we will use the PHPUnit method PHPUnit_Testframework_TestCase::getMockForAbstractClass(). $this->getMockForAbstractClass('Game_Action'. array(null. $this->assertType('Game_Action'. protected function getMockForAbstractClass($originalClassName. $this->assertType('Game_Action'. $inventory))). } Training Course PHPUnit Nick Belhomme 2010 p. $this->assertType('Game_Action'. Making sure our abstract execute is implemented. $this->getMockForAbstractClass('Game_Action'. } public function testConstructorWithParams() { $grid = $this->getMock('Game_Grid'. 30 . $inventory))). array(1. null. array($grid. $this->getMockForAbstractClass('Game_Action'. $this->getMockForAbstractClass('Game_Action'.php <?php class Game_ActionTest extends PHPUnit_Framework_TestCase { public function setUp() { $this->_action = $this->getMockForAbstractClass('Game_Action'). $this->assertType('Game_Action'. First let us build the tests for Game_Action. array('blabla'))). $callAutoload = TRUE) Which will instantiate an abstract class with a test double. } public function testConstructorWithParamsNotInventory() { $this->setExpectedException('PHPUnit_Framework_Error'). $mockClassName = ''. } public function testConstructorWithParamsNotGrid() { $this->setExpectedException('PHPUnit_Framework_Error'). array()). $callOriginalClone = TRUE. 'blabla'))).1)). $this->assertType('Game_Action'. array($grid))). null.TRAINING COURSE PHPUNIT fly. $this->getMockForAbstractClass('Game_Action'.

1)). $this->_action->setSubject($grid)). } public function testSetPersonalInventoryWithFaultyParam() { $this->setExpectedException('PHPUnit_Framework_Error'). $this->assertType('Game_Action'. } } Training Course PHPUnit Nick Belhomme 2010 p. 31 . $this->_action->getSynonyms()). } public function testSetItemIncorrectParam() { $this->setExpectedException('Exception'). } public function testSetGrid() { $grid = $this->getMock('Game_Grid'. array(1. $this->_action->setGrid($grid)). null. $item = new Game_Item_Stub(). $this->_action->getName()). } public function testGetSynonyms() { $this->assertType('array'. array(1.TRAINING COURSE PHPUNIT public function testSetItem() { require_once 'Item/Stub. $this->_action->setPersonalInventory($inventory)). } public function testSetGridWithFaultyParam() { $this->setExpectedException('PHPUnit_Framework_Error'). $this->_action->setSubject($tile)). null. $this->assertType('Game_Action'. $this->assertType('Game_Action'. $grid = $this->getMock('Game_Grid'. $this->_action->setSubject('blabla')). $this->_action->setGrid('wrong param'). $this->_action->setPersonalInventory('wrong param'). $this->assertType('Game_Action'.php'. $this->assertType('Game_Action'. } public function testSetPersonalInventory() { $inventory = $this->getMock('Game_Inventory'). $this->_action->setSubject($item)). } public function testGetName() { $this->assertEquals('action'. $tile = $this->getMock('Game_Tile').1)). $this->assertType('Game_Action'.

Game_Grid'). return $this. $this->_init(). Game_Tile. $this->_personalInventory = $inventory. } $this->_subject = $subject. } abstract public function execute(). Game_Inventory $inventory = null) { $this->_grid = $grid. return $this. protected $_synonyms = array(). protected $_name = 'action'. return $this. } 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. } public function getName() { return $this->_name. public function __construct(Game_Grid $grid = null. protected function _getExecutedMessageSuccess() { Training Course PHPUnit Nick Belhomme 2010 p. } public function getSynonyms() { return $this->_synonyms. } public function setGrid(Game_Grid $grid) { $this->_grid = $grid.TRAINING COURSE PHPUNIT Library/Game/Action. protected $_personalInventory. } public function setPersonalInventory(Game_Inventory $inventory) { $this->_personalInventory = $inventory. 32 . protected $_grid.php <?php abstract class Game_Action { protected $_subject.

TRAINING COURSE PHPUNIT echo 'action executed'. Because all the objects depending on actions need an action name to differentiate between the different actions by use of array keys. Tests/PHPUnit/Library/Game/Action/Stub. To do this we must be able to change the name of the action. goEast. } // helper function for testing public function setName($name) { $this->_name = $name. we will create a stub that will extend Game_Action and implement a setter. goWest. set the default name and add a setter. } } Training Course PHPUnit Nick Belhomme 2010 p. } 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. Game_Grid is such an object.php <?php class Game_Action_Stub extends Game_Action { public function execute() { return null. We want to give it four moving actions: GoNorth. } protected function _getExecutedMessageFailed() { echo 'action failed'. goSouth. 33 . The stub Game_Action_Stub will extend the Game_Action abstract class.

$this->assertType('Game_Grid'. $grid = new Game_Grid(2. $grid->addAction($action)). Which it will actually use as the key in the $actions array. $action->expects($this->once()) ->method('setGrid') ->with($this->isInstanceOf('Game_Grid')). } Training Course PHPUnit Nick Belhomme 2010 p. count($actions)). Thus forcing Game_Grid to accept a string value. // Same name. Adding a new action will resolve in one action registered. It also should have a fluent interface so the return value of addAction should be Game_Grid itself. 34 . $action->setGrid($this). 3) 4) public function addAction(Game_Action $action) { $this->_actions[$action->getName()] = $action. This requirement is tested by creating another action with the same name and adding it to the Grid. $action->expects($this->once()) ->method('getName') ->will($this->returnValue('stubAction')). $grid->addAction($action2)). $actions = $grid->getActions(). $initialActionsCount = count($actions).php'. $action = $this->getMock('Game_Action_Stub'). return $this. $this->assertEquals($initialActionsCount+1./Action/Stub. $action2->expects($this->once()) ->method('getName') ->will($this->returnValue('stubAction')). Adding a second with the same name will overwrite the previous one added. $this->assertType('Game_Grid'. it will overwrite the previous one set $action2 = $this->getMock('Game_Action').2).TRAINING COURSE PHPUNIT After having Game_Action in place we can continue testing Game_Grid. public function testAddActionAndGetActions() { require_once '. 2) Secondly it also should make a call to Game_Action::getName() and this method will return the string 'stubAction'. count($actions)). $actions = $grid->getActions(). $this->assertEquals($initialActionsCount+1. } 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. $actions = $grid->getActions().

Luckily for us we already defined the helper stub class earlier. leaving the others unchanged. Training Course PHPUnit Nick Belhomme 2010 p.TRAINING COURSE PHPUNIT You cannot test the call to Game_Action::setGrid if you use getMockForAbstractClass(). 35 . It will always return failure because it cannot register the call. This method replaces the abstract methods by a default implementation. Thus we use getMock() which needs a non abstract class.

$gridSize['x']).$y). 1).php <?php class Game_GridTest extends PHPUnit_Framework_TestCase { public function testConstructorNormalParamsWithGetGridSize() { $x = rand(1. $this->assertNull($grid->getTile(0.1). $gridSize['y']). 1). $paramY) { $this->setExpectedException('Exception'). $this->assertEquals($y. } public function testGetTileFromPostionWhenNoTileIsSet() { $grid = new Game_Grid(1. array(-1. public function constructorProviderBadParams() { return array( array(0. $gridSize = $grid->getGridSize(). $grid = new Game_Grid($x. array(1.0)).100). $this->assertEquals($x. } public function testAddTile() } Training Course PHPUnit Nick Belhomme 2010 p. } /** * * @dataProvider constructorProviderBadParams */ public function testConstructNegativeOrZeroParam($paramX. -1). array(0. $this->assertEquals($x.TRAINING COURSE PHPUNIT Dependencies Tests/PHPUnit/Library/Game/GridTest. $this->assertEquals($y. (string) $y). array(-1. 36 . $gridSize['y']).100). $grid = new Game_Grid($paramX. array(1. $gridSize = $grid->getGridSize(). 0). 0). $paramY). $grid = new Game_Grid((string) $x. ). $this->assertNull($grid->getTileFromPosition()). -1).1). } public function testGetTileWhenNoneIsSet() { $grid = new Game_Grid(1. $gridSize['x']). $y = rand(1.

$this->assertEquals($newPositionX. 37 . array(). $this->assertTrue($grid->isOnGrid(0.$x-1). $grid->setPosition(3.2). $grid->addTile($tile. $position['x']).0)). $this->assertEquals(0. $newPositionY = rand(0. } public function testPosition() { $x = rand(1. 'Game_Tile_Mock'.2). $grid->addTile($tile. $this->assertEquals($newPositionY.TRAINING COURSE PHPUNIT { $tile = $this->getMock('Game_Tile'. $grid = new Game_Grid(1. $newPositionY). 'Game_Tile_Mock'.1)). $grid->getTile(0. $gameActionGoSouthFound = false. count($actions)). Training Course PHPUnit Nick Belhomme 2010 p. 2. $actions = $grid->getActions(). $y = rand(1. $grid = new Game_Grid(1.1). false). $newPositionX = rand(0. $position = $grid->getPosition(). $this->assertFalse($grid->isOnGrid(0. $gameActionGoEastFound = false. $gameActionGoNorthFound = false. $position['y']).1). $this->assertType('Game_Tile_Mock'. null. } public function testDefaultGridActions() { $grid = new Game_Grid(2. $this->assertEquals(0. $position = $grid->getPosition(). $this->assertEquals(4. null.0)). 0)). $tile = $this->getMock('Game_Tile'.1). $position['y']).100). } public function testSetPositionOutsideGrid() { $this->setExpectedException('Exception'). 3). 0.100). $this->assertType('Game_Grid'. $grid->setPosition($newPositionX. false).$y-1). } public function testIsOnGrid() { $grid = new Game_Grid(1. } public function testAddTileOutsideGrid() { $this->setExpectedException('Exception'). $grid = new Game_Grid(1.1).$y). array(). $grid = new Game_Grid($x. $position['x']).

count($actions)). $action = $this->getMock('Game_Action_Stub'). $grid->addAction($action)). it will overwrite the previous one set $action2 = $this->getMock('Game_Action'). Yet sometimes to quickly localize defects. foreach ($actions as $action) { if ($action instanceof Game_Action_Go_North) { $gameActionGoNorthFound = true. $this->assertType('Game_Grid'. $this->assertType('Game_Grid'. } } Normally every test should be independent from each other. } /** * this dependency has been added to show this functionality * @depends testDefaultGridActions */ public function testAddActionAndGetActions(Game_Grid $grid) { require_once '. $this->assertTrue($gameActionGoWestFound).TRAINING COURSE PHPUNIT $gameActionGoWestFound = false. $this->assertTrue($gameActionGoSouthFound). $this->assertEquals(5. } if ($action instanceof Game_Action_Go_West) { $gameActionGoWestFound = true. } if ($action instanceof Game_Action_Go_East) { $gameActionGoEastFound = true. count($actions)). $action->expects($this->once()) ->method('setGrid') ->with($this->isInstanceOf('Game_Grid')). $actions = $grid->getActions(). $this->assertEquals(5.php'./Action/Stub. $actions = $grid->getActions(). $this->assertTrue($gameActionGoEastFound). } if ($action instanceof Game_Action_Go_South) { $gameActionGoSouthFound = true. $this->assertEquals(4. And should be able to be executed in random order. count($actions)). $grid->addAction($action2)). // Same name. } } $this->assertTrue($gameActionGoNorthFound). $action2->expects($this->once()) ->method('getName') ->will($this->returnValue('stubAction')). 38 . $action->expects($this->once()) ->method('getName') ->will($this->returnValue('stubAction')). $actions = $grid->getActions(). we want our attention to be focused on Training Course PHPUnit Nick Belhomme 2010 p. return $grid.

You create a public method which returns an array or an object which implements the Iterator interface. The test testAddActionAndGetActions depends on testDefaultGridActions. This will make our testing much more easy to understand and extend. 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. PHPUnit does not change the order in which tests are executed. A producer is a test method that yields its unit under test as return value. 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. This is why PHPUnit skips the execution of a test when a depended-upon test has failed. A consumer is a test method that depends on one or more producers and their return values. 39 . To facilitate this a dataProvider has been made available. you have to ensure that the dependencies of a test can actually be met before the test is run. We can define such a dependency by using a doc block annotation @depends [test on which the test is dependant]. Because if the defaultGridActions are not set correctly then our assertEquals for adding and getting will also fail for testAddActionAndGetActions. “Exploiting the dependencies between tests”. Purely functioning as an example we have changed some tests in the above code. For the above dependency. Because it removes redundancy. 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. testDefaultGridActions is called the producer and testAddActionAndGetActions is called the consumer. making sure that the test environment is correctly set. This improves defect localization by exploiting the dependencies between tests as shown in the above testcase. We have the possibility to pass fixtures as params in such cases. 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.TRAINING COURSE PHPUNIT relevant failing tests.

Training Course PHPUnit Nick Belhomme 2010 p. 40 .TRAINING COURSE PHPUNIT 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.

Also clean up objects for garbage collection. Training Course PHPUnit Nick Belhomme 2010 p. • setUp(): Before a test method is run. Here you can delete for example the file that was set in the setupBeforeClass(). this method is called. assertPreConditions(): After each setup but before each test. And there for is not needed to be created every time between tests.TRAINING COURSE PHPUNIT setUp. another template method called tearDown() is invoked. You may give the algorithm a specific implementation by implementing the methods. Template methods available in order of execution. 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. whether it succeeded or failed. Setting and destructing a fixture for every test can be time consuming or a lot of copy and pasting. DRY (Do Not Repeat yourself) is something every programmer should try to adhere to. This method will be called once. PHPUnit has made it easy for us to to provide us with hooks in the form of template methods. • setUpBeforeClass(): Each time when your testcase class is run this method will be called once. the latter being against good coding practice. tearDown() is where you clean allocated external resources like files or sockets. a template method called setUp() is invoked. Template methods are empty methods which are called in a predefined algorithm set by the class which owns the template methods. [THE ACTUAL TEST WILL BE EXECUTED HERE] • assertPostConditions(): After each test executed. This known state is called the fixture of the test. tearDownAfterClass(): • • • Each time when your testcase class has finished running all tests and template methods this last method will be called. tearDown(): Once the test method has finished running. 41 . this method is called. Here you can implement a check on certain aspects of your testing stage. Here you can create for example a file with content that will not change in the life cycle of the tests. Here you can implement a check on certain aspects of your testing stage. setUp() is where you create the objects against which you will test.

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. Training Course PHPUnit Nick Belhomme 2010 p. HighLowerBound: overwrite the default percentage of 70 to your minimum coverage percentage is needed to qualify as high coverage. Highlight: activates the code syntax highlighting. They will provide you with a false sense of safety. 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.xml configuration file” <logging> <log type="coverage-html" target="/tmp/PHPUnit/Coverage/" charset="UTF-8" yui="true".TRAINING COURSE PHPUNIT Testing your tests. 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. It uses the Xdebug extension. 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. lowUpperBound: overwrite the default percentage of 35 to your upper limit of max coverage percentage that is needed to qualiy as low. 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. Luckily for us. 42 . true enables javascript on click events. PHPUnit has such a feature embedded into it's service. Thus it is very important to tests your tests.

THANK YOU FOR READING. Nick Belhomme follow me on twitter: @NickBelhomme http://nickbelhomme. 43 . pretty neat I would say) • Purchase my upcoming book on an important framework. (Your boss pays and during office hours you get to become a PHPUnit expert.TRAINING COURSE PHPUNIT Increase the readability by creating smaller tests and adding assertion messages. 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. 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 Training Course PHPUnit Nick Belhomme 2010 p.

Master your semester with Scribd & The New York Times

Special offer for students: Only $4.99/month.

Master your semester with Scribd & The New York Times

Cancel anytime.