Your SlideShare is downloading. ×
Cursus phpunit
Upcoming SlideShare
Loading in...5
×

Thanks for flagging this SlideShare!

Oops! An error has occurred.

×

Introducing the official SlideShare app

Stunning, full-screen experience for iPhone and Android

Text the download link to your phone

Standard text messaging rates apply

Cursus phpunit

14,804
views

Published on

Free excerpt of my PHPUnit Training Course

Free excerpt of my PHPUnit Training Course

Published in: Technology, Education

3 Comments
36 Likes
Statistics
Notes
No Downloads
Views
Total Views
14,804
On Slideshare
0
From Embeds
0
Number of Embeds
3
Actions
Shares
0
Downloads
923
Comments
3
Likes
36
Embeds 0
No embeds

Report content
Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
No notes for slide

Transcript

  • 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