• Share
  • Email
  • Embed
  • Like
  • Save
  • Private Content

Loading…

Flash Player 9 (or above) is needed to view presentations.
We have detected that you do not have it on your computer. To install it, go here.

Like this document? Why not share!

Cursus phpunit

on

  • 15,357 views

Free excerpt of my PHPUnit Training Course

Free excerpt of my PHPUnit Training Course

Statistics

Views

Total Views
15,357
Views on SlideShare
12,705
Embed Views
2,652

Actions

Likes
35
Downloads
900
Comments
3

17 Embeds 2,652

http://blog.nickbelhomme.com 1682
http://www.igorescobar.com 641
http://jerome.tamarelle.net 221
http://www.programadorphp.org 50
https://www.ibm.com 21
http://static.slidesharecdn.com 7
http://www.techgig.com 6
http://feeds.feedburner.com 5
http://www.scoop.it 5
http://localhost:8080 4
http://translate.googleusercontent.com 3
http://navegarepressiso.blogspot.com 2
http://www.plugmasters.com.br 1
http://www.getkooky.com 1
http://www.edersonmelo.com 1
http://book.localhost 1
http://127.0.0.1:8073 1
More...

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel

13 of 3 previous next Post a comment

  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
  • Nice! Thanks for sharing. Can we download the source code from somewhere?
    Are you sure you want to
    Your message goes here
    Processing…
  • slideshare offers a download link to the PDF, happy PHPUniting
    N.
    Are you sure you want to
    Your message goes here
    Processing…
  • can i please get the pdf???
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

    Cursus phpunit Cursus phpunit Document Transcript

    • TRAINING COURSE PHPUNIT Training Course PHPUnitTraining Course PHPUnit Nick Belhomme 2010 p. 1
    • TRAINING COURSE PHPUNITGoal of this courseWe 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 domainmodel (computer text based game) and finally implement this model into Zend Framework 1.11 andup. Making you comfortable with the PHPUnit testing framework.Training Course PHPUnit Nick Belhomme 2010 p. 2
    • TRAINING COURSE PHPUNIT What where how?Training Course PHPUnit Nick Belhomme 2010 p. 3
    • TRAINING COURSE PHPUNITWhat is Unit Testing?Unit Testing is the testing of units. The tests are done to ensure that each unit is working like it isintended to work.The tests should be automatic and performed on regular intervals performing validation andverification on the correct working of the units tested. Ideally each test should be independent ofanother. A unit to be tested is a small piece of software code. Today in Object oriented programminglanguages these small pieces of software code are generally individual methods from a specificclass. Unit testing gives us a way to test our implementations, design and behavior on the classes wewrite.Today we can claim that Unit testing is a fundamental part of quality modern software development.Benefits of Unit TestingUnit 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) orbecause changes made to the code, Unit Tests will quickly show you were the error or bug has beenintroduced. Allowing you to quickly locate and fix the bug. In the end speeding up developmenttime.Because of the tests in place you as a developer will feel more confident in your code and in yourfuture changes. Reflecting that confidence to customers, management and other developers.Training Course PHPUnit Nick Belhomme 2010 p. 4
    • TRAINING COURSE PHPUNITTesting pieces of softwareMostly for (PHP) developers testing their code was programming something and displaying it in thebrowser. 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 thebrowser 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 thattest eternally embedded in your test-suite. Martin-Fowler”Every programmer makes mistakes. What differentiates the good from the bad is that the goodprogrammer uses tests to detect his mistakes ASAP. Reducing debugging costs to a minimum andmaking 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 setIf we want to check if the behavior of the (array) cast operator is working as intended we can add asimple check<?php$fixture = new stdClass();$fixture->id = 1248;$fixture->name = Robin Hood;echo is_object($fixture), PHP_EOL;echo $fixture->id == 1248, PHP_EOL;echo $fixture->name == Robin Hood, PHP_EOL;$fixture = (array) $fixture;echo is_array($fixture),PHP_EOL;echo $fixture[id] == 1248, PHP_EOL;echo $fixture[name]== Robin Hood, PHP_EOL;Training Course PHPUnit Nick Belhomme 2010 p. 5
    • TRAINING COURSE PHPUNITEverything is working as expected because we get for the 6 tests all 1But at this time the tests are not automated and still require a human to check if every test hasreturned 1.Scanning over a big list of “ones” could get tiring and frustrating if you have to do it several times aday.We should automate this so that the tests only report unwanted behavior.Lets 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 testsuites 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 specificallyPHPUnit.Training Course PHPUnit Nick Belhomme 2010 p. 6
    • TRAINING COURSE PHPUNITPHPUnit as a part of the XUnit FamilyThe XUnit family is a collective of various code-driven testing frameworks. They provide anautomated solution with no need to write the same tests many times, and no need toremember what the result should be of each test. The name XUnit is a derivation of JUnit the first tobe 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 andPHPUnit by Sebastian Bergmann.In this course we have chosen to elaborate PHPUnit. The reason is because it is more widelyadopted for PHP. Which is reflected by the adoption for it in major PHP frameworks like ZendFramework and Symfony. Another reason is SimpleTest is not really maintained anymore.When to create Unit testsWhen you have classes which arent covered yet by tests you should start to setup a testing suite tomake sure your code gets validated and checked on a regular interval. If you put those tests in placeyou will become more confident in adding extra functionality and refactoring your existing codebase.You could follow this methodology. Writing first the classes and then the tests. While this is betterthan 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 putfirst the tests in place and then the implementation. This is exactly what Test Driven DevelopmentTDD 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 thispath. And puts tests in place before even implementing a single piece of application code.But you shouldnt think in terms of tests but in terms of behavior. This brings us to Behavior DrivenDevelopment. Which has the benefits of TDD but shifts the focus.“So if its not about testing, whats it about?Its about figuring out what you are trying to do before you run off half-cocked to try to do it. Youwrite a specification that nails down a small aspect of behaviour in a concise, unambiguous, andexecutable form. Its that simple. Does that mean you write tests? No. It means you writespecifications of what your code will have to do. It means you specify the behaviour of your codeahead of time. But not far ahead of time. In fact, just before you write the code is best because thatswhen you have as much information at hand as you will up to that point. Like well done TDD,Training Course PHPUnit Nick Belhomme 2010 p. 7
    • TRAINING COURSE PHPUNITyou work in tiny increments... specifying one small aspect of behaviour at a time, then implementingit. When you realize that its all about specifying behaviour and not writing tests, your point of viewshifts. Suddenly the idea of having a Test class for each of your production classes is ridiculouslylimiting. And the thought of testing each of your methods with its own test method (in a 1-1relationship) will be laughable. —Dave Astels.Training Course PHPUnit Nick Belhomme 2010 p. 8
    • TRAINING COURSE PHPUNIT Getting StartedTraining Course PHPUnit Nick Belhomme 2010 p. 9
    • TRAINING COURSE PHPUNITInstalling PHPUnitInstalling PHPUnit is straight forward. You use the Pear Installer, set up your path (which shouldalready be done for PEAR) and thats 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/PHPUnitDefining the projectWe are going to build a text based adventure game. The setup of the game will be much alike theold adventure games of Sierra and Lucas Arts but then purely text based.We will have a character (you) which can travel from location to location, picking up objects,manipulating the objects and talking to characters.So we will need:Grid : the gaming world holding all of the locations. We should be able to change position on the grid.Training Course PHPUnit Nick Belhomme 2010 p. 10
    • TRAINING COURSE PHPUNITTile: 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 totalk 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. Thisway same name clashes will most likely not occur.Training Course PHPUnit Nick Belhomme 2010 p. 11
    • TRAINING COURSE PHPUNITWriting your first test case: Implementing the GridFirst requirement is we will have to create a class called Game_Grid. Lets define this in a unit test.Tests/PHPUnit/Library/Game/GridTest.php<?phprequire_once PHPUnit/Framework.php;require_once ../../../../Library/Game/Grid.php;class Game_GridTest extends PHPUnit_Framework_TestCase{ public function testConstructor() { $grid = new Game_Grid(); }}running the test will result a FATAL ERROR!Training Course PHPUnit Nick Belhomme 2010 p. 12
    • TRAINING COURSE PHPUNITThis is because we didnt create the Game/Grid.php file yet OR we havent setup our include pathcorrectly OR the file Grid.php doesnt include a class of the type Game_Grid. Which cant be autoloaded.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 itwith Test.The class extends PHPUnit_Framework_TestCase which will give you access to the full power ofTraining Course PHPUnit Nick Belhomme 2010 p. 13
    • TRAINING COURSE PHPUNITPHPUnit. 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 withthe 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 willexecute them in the order defined. You should make sure every test is independent of another. Andfinally all tests can have multiple assertions. In our test we didnt include any assertions yet but theywill 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 theTraining Course PHPUnit Nick Belhomme 2010 p. 14
    • TRAINING COURSE PHPUNIT test directory.2) Then we start by creating our unit test We created our first test called GridTest.php with the above Unit test embedded. All test cases respect the following file naming convention: [class-name-to-test] + “Test.php”3) We execute the command: "phpunit GridTest.php" in the folder where the test is located. This will result in a fatal error: require_once... If you get an error that your operating system doesnt recognize phpunit, you havent 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.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 foundTraining Course PHPUnit Nick Belhomme 2010 p. 15
    • TRAINING COURSE PHPUNIT6) Add the class definition into the Grid.php file.Library/Game/Grid.php<?phpclass Game_Grid {}7) run the test once more and see it pass.Bootstrapping PHPUnit test casesBecause a lot of basic functions are repetitive and should be loaded for each test we could bootstrapit. In the bootstrap we can for instance set the include path, or an auto loader. Or any other directivethat your application needs for running correctly. You can load a bootstrap file by passing it as aparameterPHPUnit --bootstrap [bootstrap file] [test case to run] in example: PHPUnit --bootstrap Bootstrap.php GridTest.phpIn our test suite we will be bootstrapping our tests. This will include a basic auto loader so we donot have to write all the includes all the time. When we run our application itself we will also autoload it. Everything will be auto loaded in the application. So why should our unit tests be anydifferent.Create a file called Bootstrap.php (you can call it whatever you want but it is always a good idea tostick to naming conventions. Because PHPUnit regards it as a bootstrap, you might as well name itTraining Course PHPUnit Nick Belhomme 2010 p. 16
    • TRAINING COURSE PHPUNITthat way).In the bootstrap we make sure our PHPUnit framework is loaded and implement a simple autoloader.<?phprequire_once PHPUnit/Framework.php;function __autoload($className){ $path = explode(_, $className); $root = ; if ($path[0] == App) { $root = ../../../../Application/Library/; array_shift($path); } else if ($path[0] == Game) { $root = ../../../../Library/Game/; array_shift($path); } require_once $root.implode(DIRECTORY_SEPARATOR, $path)..php;}Now that we have the auto loader in place we can remove the require_once from our tests.Training Course PHPUnit Nick Belhomme 2010 p. 17
    • TRAINING COURSE PHPUNITConfigure PHPUnit with a phpunit.xml configuration fileYou can pass a lot of optional parameters to PHPUnit. For a complete list run the command withoutany test to run, any parameter or with the parameter --helpPHPUnitPHPUnit --helpWe 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 commandfrom. Of course you can name it any other file name. But we suggest you sticking to that. If youneed multiple configuration files you can name them whatever you want.In example:phpunit.xml for normal testingphpunitCruisecontrol.xml for a special cruisecontrol setup which is a part of the continuousintegration methodology.Lets take a closer look at the configuration file we will setup:Training Course PHPUnit Nick Belhomme 2010 p. 18
    • TRAINING COURSE PHPUNITTests/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 suitecalled 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. You do not have to specify the XML declaration for PHPUnit to understand it. But as always it is best practice.After specifying these configurations PHPUnit can find all tests automatically and you do not haveto write PHPUnit --bootstrap Bootstrap.php GridTest.php anymore.The following command will suffice:PHPUnitTraining Course PHPUnit Nick Belhomme 2010 p. 19
    • TRAINING COURSE PHPUNITTraining Course PHPUnit Nick Belhomme 2010 p. 20
    • TRAINING COURSE PHPUNITWriting AssertionsWe 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.Lets first start implementing the constructor. We want it to accept the number of vertical tiles andhorizontal tiles as integers. Then we want to be able to get the grid size set.We define the requirements by means of a Test.<?phppublic 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: PHPUnitAs you can see there is a fatal error: call to undefined method.Lets implement these requirements into Game_ClassTraining Course PHPUnit Nick Belhomme 2010 p. 21
    • TRAINING COURSE PHPUNIT<?phpclass 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 ErrorsOkay so far so good. But what happens if we pass negative values or no integers at all? Time to putit 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. Bythinking of ways your application might be used and putting it thru the test, you develop morerobust and safer software. It also makes you understand your code and the requirements better.1. Test for boundary conditions.2. Test for both success and failure.Training Course PHPUnit Nick Belhomme 2010 p. 22
    • TRAINING COURSE PHPUNIT3. 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 frameworkBoth work equally well but you should choose one. Remember consistency makes reading muchmore comprehensible.The test will show us that this is not yet thrown by our implementationTraining Course PHPUnit Nick Belhomme 2010 p. 23
    • TRAINING COURSE PHPUNIT 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.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
    • TRAINING COURSE PHPUNITThis 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.assertTraining Course PHPUnit Nick Belhomme 2010 p. 25
    • TRAINING COURSE PHPUNITIncomplete and skipped testsGenerally when you are working on a new test case class, you might want to begin by writingempty 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. PHPUnitwill tell you on each run you did not yet implement the required test.Training Course PHPUnit Nick Belhomme 2010 p. 26
    • TRAINING COURSE PHPUNITHaving 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 isactually successful or just not yet implemented. Calling $this->fail() in the unimplemented testmethod does not help either, since then the test will be interpreted as a failure. This would be just aswrong as interpreting an unimplemented test as a success.To indicate that a certain test should be skipped we can use the method$this->markTestSkipped( The MySQLi extension is not available. );This is useful when some precondition is not met. For instance a mySQLi extention is not available.The descriptive messages passed as a parameter can be any string you like.Training Course PHPUnit Nick Belhomme 2010 p. 27
    • TRAINING COURSE PHPUNITTest DoublesWe 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 addTiles. How can we test this when the Game_Tile class hasnt been written yet? For this reasonamongst 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 beaware of it and thus not use it.Mocking from stdClass will not work if there is interaction with Game_Tile from within the methodGame_Grid::addTile(). When such an implementation is needed we will need to create theGame_Tile class and implement the called methods, properties or whatever is needed. Then we canuse the Game_Tile class as a blueprint like we did with stdClass. Making sure no real actions areperformed when called upon.“Sometimes it is just plain hard to test the system under test (SUT) because it depends on othercomponents that cannot be used in the test environment. This could be because they arentavailable, they will not return the results needed for the test or because executing them would haveundesirable side effects. In other cases, our test strategy requires us to have more control orvisibility 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 doesnt have to behave exactly likethe real DOC; it merely has to provide the same API as the real one so that the SUT thinks it is thereal one! [Gerard Meszaros - Meszaros2007] “Training Course PHPUnit Nick Belhomme 2010 p. 28
    • TRAINING COURSE PHPUNIT$this->getMock() method of PHPUnit accepts several parameters.protected function getMock($originalClassName, $methods = array(), array$arguments = array(), $mockClassName = , $callOriginalConstructor = TRUE,$callOriginalClone = TRUE, $callAutoload = TRUE)As you can see only the first parameter is mandatory all others are optional.The first parameter is the name of an existing class needed to be mocked. PHPUnit will replace theimplementation from all the methods from this class with “return null;” unless otherwise statedin parameter two or by further configuration of the mock object returned. The only method that willremain unchanged by default is the constructor which can also be mocked by setting the fifthparameter to false.As we said parameter two gives you the possibility to specify only those methods for which youwant PHPUnit to change the implementation. Only the methods whose names are in the array arereplaced 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 constructorThe seventh parameter can be used to disable __autoload() during the generation of the testdouble class.After implementing the tile functionality into the grid, we also need to implement the actions thatcan be set. We want to be able to move from tile to tile on the grid. This means we have to be ableto add and get actions from Game_Grid. The tricky part in this implementation is that Game_Actionshould be aware on which Game_Item, Game_Tile and or Game_Grid object it is acting its magicon. This was defined in the project requirements. This implies that when adding an action to the gridthe 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 configuredreturn values. You can use a stub to "replace a real component on which the SUT dependsso that the test has a control point for the indirect inputs of the SUT. This allows the test to force theSUT down paths it might not otherwise execute".Our implementation of Game_Action will be an abstract one. We could make it a fully implementedclass which accepts a name by a setter. But we do not want that, because we feel like every action inthe game should have a real hardcoded implementation and not generated on the fly. Of course thiscan change in time. In example future implementations may come from a CMS tool and then wewill need the setters. But for now we do not want a mix of real objects with objects generated on theTraining Course PHPUnit Nick Belhomme 2010 p. 29
    • TRAINING COURSE PHPUNITfly.First let us build the tests for Game_Action.To test Game_Action we will use the PHPUnit methodPHPUnit_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 isimplemented.Tests/PHPUnit/Library/Game/ActionTest.php<?phpclass Game_ActionTest extends PHPUnit_Framework_TestCase{ public function setUp() { $this->_action = $this->getMockForAbstractClass(Game_Action); } public function testConstructorWithParams() { $grid = $this->getMock(Game_Grid, null, array(1,1)); $inventory = $this->getMock(Game_Inventory, null, array()); $this->assertType(Game_Action, $this->getMockForAbstractClass(Game_Action, array($grid, $inventory))); $this->assertType(Game_Action, $this->getMockForAbstractClass(Game_Action, array($grid))); $this->assertType(Game_Action, $this->getMockForAbstractClass(Game_Action, array(null, $inventory))); $this->assertType(Game_Action, $this->getMockForAbstractClass(Game_Action, array(null, null))); } public function testConstructorWithParamsNotGrid() { $this->setExpectedException(PHPUnit_Framework_Error); $this->assertType(Game_Action, $this->getMockForAbstractClass(Game_Action, array(blabla))); } public function testConstructorWithParamsNotInventory() { $this->setExpectedException(PHPUnit_Framework_Error); $this->assertType(Game_Action, $this->getMockForAbstractClass(Game_Action, array(null, blabla))); }Training Course PHPUnit Nick Belhomme 2010 p. 30
    • TRAINING COURSE PHPUNIT public function testSetItem() { require_once Item/Stub.php; $item = new Game_Item_Stub(); $this->assertType(Game_Action, $this->_action->setSubject($item)); $tile = $this->getMock(Game_Tile); $this->assertType(Game_Action, $this->_action->setSubject($tile)); $grid = $this->getMock(Game_Grid, null, array(1,1)); $this->assertType(Game_Action, $this->_action->setSubject($grid)); } public function testSetItemIncorrectParam() { $this->setExpectedException(Exception); $this->assertType(Game_Action, $this->_action->setSubject(blabla)); } public function testGetName() { $this->assertEquals(action, $this->_action->getName()); } public function testGetSynonyms() { $this->assertType(array, $this->_action->getSynonyms()); } public function testSetGrid() { $grid = $this->getMock(Game_Grid, null, array(1,1)); $this->assertType(Game_Action, $this->_action->setGrid($grid)); } public function testSetGridWithFaultyParam() { $this->setExpectedException(PHPUnit_Framework_Error); $this->_action->setGrid(wrong param); } public function testSetPersonalInventory() { $inventory = $this->getMock(Game_Inventory); $this->assertType(Game_Action, $this->_action->setPersonalInventory($inventory)); } public function testSetPersonalInventoryWithFaultyParam() { $this->setExpectedException(PHPUnit_Framework_Error); $this->_action->setPersonalInventory(wrong param); }}Training Course PHPUnit Nick Belhomme 2010 p. 31
    • TRAINING COURSE PHPUNITLibrary/Game/Action.php<?phpabstract class Game_Action{ protected $_subject; protected $_name = action; protected $_synonyms = array(); protected $_grid; protected $_personalInventory; public function __construct(Game_Grid $grid = null, Game_Inventory $inventory = null) { $this->_grid = $grid; $this->_personalInventory = $inventory; $this->_init(); } public function setSubject($subject) { if (!($subject instanceOf Game_Item) && !($subject instanceOf Game_Tile) && !($subject instanceOf Game_Grid)) { throw new Exception(Subject passed should be of type Game_subject, Game_Tile, Game_Grid); } $this->_subject = $subject; return $this; } public function getName() { return $this->_name; } public function getSynonyms() { return $this->_synonyms; } public function setGrid(Game_Grid $grid) { $this->_grid = $grid; return $this; } public function setPersonalInventory(Game_Inventory $inventory) { $this->_personalInventory = $inventory; return $this; } abstract public function execute(); protected function _getExecutedMessageSuccess() {Training Course PHPUnit Nick Belhomme 2010 p. 32
    • TRAINING COURSE PHPUNIT echo action executed; } protected function _getExecutedMessageFailed() { echo action failed; } protected function _executeSuccess() { // template method } protected function _executeFailed() { // template method } protected function _init() { // template method }}Now that Game_Action is implemented and tested it is time to create a helper stub. Because all theobjects depending on actions need an action name to differentiate between the different actions byuse 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 andadd a setter.Tests/PHPUnit/Library/Game/Action/Stub.php<?phpclass Game_Action_Stub extends Game_Action{ public function execute() { return null; } // helper function for testing public function setName($name) { $this->_name = $name; }}Training Course PHPUnit Nick Belhomme 2010 p. 33
    • TRAINING COURSE PHPUNITAfter having Game_Action in place we can continue testing Game_Grid.public function testAddActionAndGetActions(){ require_once ./Action/Stub.php; $action = $this->getMock(Game_Action_Stub); $action->expects($this->once()) ->method(setGrid) ->with($this->isInstanceOf(Game_Grid)); $action->expects($this->once()) ->method(getName) ->will($this->returnValue(stubAction)); $grid = new Game_Grid(2,2); $actions = $grid->getActions(); $initialActionsCount = count($actions); $this->assertType(Game_Grid, $grid->addAction($action)); $actions = $grid->getActions(); $this->assertEquals($initialActionsCount+1, count($actions)); // Same name, it will overwrite the previous one set $action2 = $this->getMock(Game_Action); $action2->expects($this->once()) ->method(getName) ->will($this->returnValue(stubAction)); $this->assertType(Game_Grid, $grid->addAction($action2)); $actions = $grid->getActions(); $this->assertEquals($initialActionsCount+1, count($actions));}We define in the test:1) there should be a method Game_Grid::addAction and it should make one call to the method setGrid on the passed Game_Action instance passing the Game_Grid as a param.2) Secondly it also should make a call to Game_Action::getName() and this method will return the string stubAction. Thus forcing Game_Grid to accept a string value. Which it will actually use as the key in the $actions array.3) It also should have a fluent interface so the return value of addAction should be Game_Grid itself.4) Adding a new action will resolve in one action registered. Adding a second with the same name will overwrite the previous one added. This requirement is tested by creating another action with the same name and adding it to the Grid.public function addAction(Game_Action $action){ $this->_actions[$action->getName()] = $action; $action->setGrid($this); return $this;}Training Course PHPUnit Nick Belhomme 2010 p. 34
    • TRAINING COURSE PHPUNIT You cannot test the call to Game_Action::setGrid if you use getMockForAbstractClass(). This method replaces the abstract methods by a default implementation, leaving the others unchanged. It will always return failure because it cannot register the call. Thus we use getMock() which needs a non abstract class. Luckily for us we already defined the helper stub class earlier.Training Course PHPUnit Nick Belhomme 2010 p. 35
    • TRAINING COURSE PHPUNITDependenciesTests/PHPUnit/Library/Game/GridTest.php<?phpclass Game_GridTest extends PHPUnit_Framework_TestCase{ public function testConstructorNormalParamsWithGetGridSize() { $x = rand(1,100); $y = rand(1,100); $grid = new Game_Grid($x,$y); $gridSize = $grid->getGridSize(); $this->assertEquals($x, $gridSize[x]); $this->assertEquals($y, $gridSize[y]); $grid = new Game_Grid((string) $x, (string) $y); $gridSize = $grid->getGridSize(); $this->assertEquals($x, $gridSize[x]); $this->assertEquals($y, $gridSize[y]); } public function constructorProviderBadParams() { return array( array(0, 0), array(0, 1), array(1, 0), array(-1, 1), array(1, -1), array(-1, -1), ); } /** * * @dataProvider constructorProviderBadParams */ public function testConstructNegativeOrZeroParam($paramX, $paramY) { $this->setExpectedException(Exception); $grid = new Game_Grid($paramX, $paramY); } public function testGetTileWhenNoneIsSet() { $grid = new Game_Grid(1,1); $this->assertNull($grid->getTile(0,0)); } public function testGetTileFromPostionWhenNoTileIsSet() { $grid = new Game_Grid(1,1); $this->assertNull($grid->getTileFromPosition()); } public function testAddTile()Training Course PHPUnit Nick Belhomme 2010 p. 36
    • TRAINING COURSE PHPUNIT { $tile = $this->getMock(Game_Tile, null, array(), Game_Tile_Mock, false); $grid = new Game_Grid(1,1); $this->assertType(Game_Grid, $grid->addTile($tile, 0, 0)); $this->assertType(Game_Tile_Mock, $grid->getTile(0,0)); } public function testAddTileOutsideGrid() { $this->setExpectedException(Exception); $tile = $this->getMock(Game_Tile, null, array(), Game_Tile_Mock, false); $grid = new Game_Grid(1,1); $grid->addTile($tile, 2, 3); } public function testIsOnGrid() { $grid = new Game_Grid(1,1); $this->assertTrue($grid->isOnGrid(0,0)); $this->assertFalse($grid->isOnGrid(0,1)); } public function testPosition() { $x = rand(1,100); $y = rand(1,100); $grid = new Game_Grid($x,$y); $position = $grid->getPosition(); $this->assertEquals(0, $position[x]); $this->assertEquals(0, $position[y]); $newPositionX = rand(0,$x-1); $newPositionY = rand(0,$y-1); $grid->setPosition($newPositionX, $newPositionY); $position = $grid->getPosition(); $this->assertEquals($newPositionX, $position[x]); $this->assertEquals($newPositionY, $position[y]); } public function testSetPositionOutsideGrid() { $this->setExpectedException(Exception); $grid = new Game_Grid(1,1); $grid->setPosition(3,2); } public function testDefaultGridActions() { $grid = new Game_Grid(2,2); $actions = $grid->getActions(); $this->assertEquals(4, count($actions)); $gameActionGoNorthFound = false; $gameActionGoEastFound = false; $gameActionGoSouthFound = false;Training Course PHPUnit Nick Belhomme 2010 p. 37
    • TRAINING COURSE PHPUNIT $gameActionGoWestFound = false; foreach ($actions as $action) { if ($action instanceof Game_Action_Go_North) { $gameActionGoNorthFound = true; } if ($action instanceof Game_Action_Go_East) { $gameActionGoEastFound = true; } if ($action instanceof Game_Action_Go_South) { $gameActionGoSouthFound = true; } if ($action instanceof Game_Action_Go_West) { $gameActionGoWestFound = true; } } $this->assertTrue($gameActionGoNorthFound); $this->assertTrue($gameActionGoEastFound); $this->assertTrue($gameActionGoSouthFound); $this->assertTrue($gameActionGoWestFound); return $grid; } /** * this dependency has been added to show this functionality * @depends testDefaultGridActions */ public function testAddActionAndGetActions(Game_Grid $grid) { require_once ./Action/Stub.php; $action = $this->getMock(Game_Action_Stub); $action->expects($this->once()) ->method(setGrid) ->with($this->isInstanceOf(Game_Grid)); $action->expects($this->once()) ->method(getName) ->will($this->returnValue(stubAction)); $actions = $grid->getActions(); $this->assertEquals(4, count($actions)); $this->assertType(Game_Grid, $grid->addAction($action)); $actions = $grid->getActions(); $this->assertEquals(5, count($actions)); // Same name, it will overwrite the previous one set $action2 = $this->getMock(Game_Action); $action2->expects($this->once()) ->method(getName) ->will($this->returnValue(stubAction)); $this->assertType(Game_Grid, $grid->addAction($action2)); $actions = $grid->getActions(); $this->assertEquals(5, count($actions)); }}Normally every test should be independent from each other. And should be able to be executed inrandom order. Yet sometimes to quickly localize defects, we want our attention to be focused onTraining Course PHPUnit Nick Belhomme 2010 p. 38
    • TRAINING COURSE PHPUNITrelevant failing tests. This is why PHPUnit skips the execution of a test when a depended-upon testhas failed. This improves defect localization by exploiting the dependencies between tests as shownin 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 gettingwill also fail for testAddActionAndGetActions. We can define such a dependency by using a docblock 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 testenvironment is correctly set.For the above dependency, testDefaultGridActions is called the producer andtestAddActionAndGetActions is called the consumer. 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.The DataProviderSometimes we want to test the same method with different params which should all result in the anoutcome 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 forExceptions and PHP Errors” to use the dataProvider annotation. This will make our testing muchmore 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 thecontents of the array as its arguments.You create a public method which returns an array or an object which implements the Iteratorinterface. 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
    • 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.Training Course PHPUnit Nick Belhomme 2010 p. 40
    • TRAINING COURSE PHPUNITsetUp, tearDown and other template methodsOne of the most time-consuming parts of writing tests is writing the code to setup the world to aknown state and then return it to its original state when the test is complete. This known state iscalled 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 everyprogrammer 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 classwhich owns the template methods. You may give the algorithm a specific implementation byimplementing the methods.Template methods available in order of execution. • setUpBeforeClass(): Each time when your testcase class is run this method will be called once. Here you can create for example a file with content that will not change in the life cycle of the tests. And there for is not needed to be created every time between tests. • setUp(): Before a test method is run, a template method called setUp() is invoked. setUp() is where you create the objects against which you will test. • assertPreConditions(): After each setup but before each test, this method is called. Here you can implement a check on certain aspects of your testing stage. [THE ACTUAL TEST WILL BE EXECUTED HERE] • assertPostConditions(): After each test executed, this method is called. Here you can implement a check on certain aspects of your testing stage. • tearDown(): Once the test method has finished running, whether it succeeded or failed, another template method called tearDown() is invoked. tearDown() is where you clean allocated external resources like files or sockets. Also clean up objects for garbage collection. • tearDownAfterClass(): Each time when your testcase class has finished running all tests and template methods this last method will be called. This method will be called once. Here you can delete for example the file that was set in the setupBeforeClass().Training Course PHPUnit Nick Belhomme 2010 p. 41
    • TRAINING COURSE PHPUNITTesting your tests, code coverageAfter creating a test for some functionality you should test the testcase for code coverage. What usedo tests have if they do not cover all of your application code. They will provide you with a falsesense of safety. Thus it is very important to tests your tests.Luckily for us, PHPUnit has such a feature embedded into its 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/ ActionTestWe 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 testexecution.type: specifies the type of loggingtarget: the target directory where the log should be written tocharset: the character encoding for the logyui: 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 coveragepercentage that is needed to qualiy as low.HighLowerBound: overwrite the default percentage of 70 to your minimum coverage percentage isneeded to qualify as high coverage.Training Course PHPUnit Nick Belhomme 2010 p. 42
    • TRAINING COURSE PHPUNITIncrease 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-trainingTraining Course PHPUnit Nick Belhomme 2010 p. 43