Practical PHP Testing
Practical PHP Testing
Practical PHP Testing
Table of Contents
Introduction.......................................................................5 About the author................................................................5
Contacts.........................................................................................5 Donations.......................................................................................6 Errata..............................................................................................6
Disclaimer..........................................................................7
Cop right!......................................................................................7 Ac"no#ledgements.........................................................................7
Introduction
This practical testing book is aimed to php developers and features the articles from the Practical php testing series of my blog, plus new content only available in this book: preface to explain why we should care about testing php applications. bonus chapter on TDD theory and a case study. code samples, some of whom were originally kept on pastebin.com; this php code complements many chapters. Code is highlighted with a special background. ets of TDD exercises at the end of each chapter. Test!"riven "evelopment is a practice where tests are written before the production code. This is not a book specifically on T"", but these exercises will help you grasp the fundamentals of this methodology and its advantages: testable code, effective test suite and the good design that ensues. The T"" theory #chapter $%& closes the circle. ' glossary that substitutes external links to wiki and other posts, to not interrupt your reading with terms lookup.
Contacts
5ou are free to contact me on my blog, or in other places on the web: piccoloprincipeazzurro at gmail dot com http://twitter.com/giorgiosironi
Donations
*f you find helpful my work and you want to support it someway, * accept donations: http:99doiop.com9giorgiosironipaypal 3r, 8ust in case the redirected url above does not work: https:99www.paypal.com9cgi!bin9webscr: cmd;<donations=business;7>?(47@(A1,BC=lc;)@=item<name;)ior gio D+% ironi=item<number;invisibletotheeye=currency<code;10E=bn;PP D+d"onations@>D/abtn<donate<F)D+egifD/a4onGosted
Errata
Practical Php Testing $st edition #"ecember +, +%%,&. 1rrata web page: http:99giorgiosironi.blogspot.com9+%%,9$+9practical!php! testing!errata.html 0nless a further edition may be publicated, refer to the linked web page for corrections.
Disclaimer
This book has been written for educational and informational purposes and the author made every reasonable attempt to achieve accuracy of the content. The author assumes no responsibility for errors or omissions and is not liable for incorrect or no longer up!to!date information may cause to your work.
Copyright?
H +%%, )iorgio ironi This ebook is licensed under a Creative Commons 'ttribution! 4oncommercial! hare 'like /.% 0nited tates Ficense. * actually encourage you to give this ebook to your friends and fellows if they are interested, electronically or by printing. * think the book has a better effect if read in its entirety, but you can also extract part of it as long as you cite the original author as the source #)iorgio ironi&. *f you publish a part of it online, * would be thankful for a link to http:99giorgiosironi.blogspot.com, and glad if you contact me to let me know my work has been useful to you. * don2t charge anything and you should definitely IpirateJ this book but please maintain the correct attribution. :&
Acknowledgements
The ebook cover is a parody of an illustration from The Fittle Prince by 'ntoine de aint!1xupKry, where an elephant is eaten by a snake in its entirety. The original elephant is replaced by a elePGPant image from a photography, courtesy of Eaphael "ohms #http:99twitter.com9rdohms&. 'll other images in this book are create from scratch or provenient from 7ikimedia Commons. This book is dedicated to Biki, who tolerates me when * get in the zone and dive into programming without caring about the outside world.
&
Disclaimer
*f the answer to these .uestions is more than very few, it2s likely that you should give automated testing a chance. Testing can be handled professionally, by writing a test suite that you can run at the push of a button and from the command line. *f well!written, this suite will show you a list of localiLed errors and where to go to fix them. *t will eliminate most of the debugging from your day. Gow testing does so: nit testing is the testing flavor * prefer and which gives much advantages. ince Car examples are very popular, * would say that it is comparable to testing a car by dismantling it and running workbench tests on every single piece, with the difference that the pieces do not wear out and that the process takes from some milliseconds to a minute #since you can sometimes decide that you are only interested in exercising the engine or the doors&. There are other testing paradigms, such as integration testing, but they are less effective in locating problems and bugs than class!focused test cases #since the typical unit under test is a single class&, and in dictating an 'pi and an architecture. 0nit testing means freedom of development, without the fear of breaking something and not noticing it: running a full test on the components affected by code modifications takes only seconds. *t also results in well! designed code, because cohesion and decoupling are testability re.uirements. Code is a children game: it can be a miniature car which you can2t alter, or it can be a set of Fego bricks which you can always construct new buildings and starships with. *2m sure the ma8ority of this book readers were the children that liked disassembling games.
'*
Pre$ace% #h testing!
4ow that we are adults we do not play with Fego bricks anymore, but with classes. The difference resides in if we want to play with decoupled and testable classes or with an untestable mess. Mery often * heard people saying Ithis class is too difficult to testJ. (y response is: refactor the design to aid testability. Testing is not a tedious non!functional specification which forces the developer to add otherwise unuseful code: it is the driving force behind good architecture. 's you will discover in T"" exercises, if we write code to satisfy a test suite, and then throw away the test suite, we are still left with a better product than with the result of cowboy coding. (ore focused classes, small interfaces and good 'pi are the by!products of unit testing. Testing is not a code base detail in this book2s weltanschauung: it is the starting point.
''
0sing a root shell #or a administrator on if you develop on other operating systems& instead of sudo is totally e.uivalent. 'dministrator permissions are usually mandatory to install Pear packages. These commands tell pear to add the channel of PGP0nit developers, and to install the PHPUnit package from the now!available phpunit channel.
'2
The chosen release is the latest stable package; at the time of this writing, the /.O version. *f the installation is successful, you now have a phpunit executable available from the command line. This is where you will run tests; if you use an *"1, probably there is a menu for running tests that will show you the command line output #and you should also install phpunit from the *"1 interface to make it discover the new tool&. *n this book, * prefer to use the command!line interface to not add other levels of indirection which could get in the way of learning. 1verything you can do with an *"1 like 4etbeans or 1clipse, you can surely do in the command!line environment. @efore exploring the endless possibilities of testing, let2s write our first one: the simplest test that could possibly work. * saved this php code in (yTest.php:
class MyTest extends PHPUnit_Framework_Test ase ! pu"lic #unction test ompares$um"ers%& ! 'this-(assertTrue%) ** )&+ , ,
7hat is a test: 'nd a test case: ' test case is constituted by a method by a class which extends PHPUnit_Framework_TestCase, which as its name tells is an abstract test case class provided by the PGP0nit framework. 7hen developing a ob8ect!oriented application, you may want to start with one test case per every class you want to test #and if you2re going the T"" way every class will be tested&, thus there will be a $:$ correspondence between classes and test cases. >or the moment, we don2t want to go too fast and we simply write a class that tests php common functionality. !very test is a method which by convention starts with the keyword 2test2. 'lso for convention, the method name should tell what operation the system under test is capable, in this test . !very method will be run independently in an isolated environment, and will make some assertion on what should happen. assertTrue#& is one of the many assertP#& method inherited from the abstract test case, which declares the test failed if an argument different from true is passed to it. The test as it is written now should pass. *n fact, we can simply run it and find out:
Practical Php Testing -.ior.io/Marty012' phpunit MyTest.php PHPUnit 3.4.5 "y 6e"astian 7er.mann. . Time0 ) second 89 %) test: ) assertion&
'3
"nstant feedback is one of the pillars of T"" and of unit testing in general: the code in tests should instance your classes and exercising their functionality to ensure they don2t blow up and respect the specifications. 7ith the phpunit script, it2s very simple and fast to run a test case or a group of them after you have made a change to your class and make sure you haven2t break an existing feature. The result of phpunit run is easily interpretable: a dot #.& for every test method which passed #without failed assertions&, and a statistic of the number of tests and assertions encountered. Fet2s try to make it fail, changing 1 1 to 1 !:
-.ior.io/Marty012' phpunit MyTest.php PHPUnit 3.4.5 "y 6e"astian 7er.mann. F Time0 5 seconds There was ) #ailure0 )& MyTest00test ompares$um"ers Failed assertin. that is true. /media/sd")/.ior.io/MyTest.php0; F<=>U?@6A Tests0 ): <ssertions0 ): Failures0 ).
>or every failed test, you get an > instead of the point in the summary. 3ther letters can be encountered, for instance the 1 if the test caused no assertion to fail but it raised an exception or a php error. The only passed tests are the one which present a dot: you should strive for a long list of dots that fill more than one row. 5ou also get a description of the assertion which has failed and in what test case# method and line it resides. ince you can run many test cases as a single command, this is very useful to localiLe defects and regression. This time the test has failed because it is bad written: Lero is not e.ual to
'-
one and php is right in giving out false. @ut assertTrue#& does not know this and in the next chapter we2ll write some tests which works upon userland code and it is in fact useful to detect if production classes are still satisfying all their re.uirements.
TDD exercises
$.$ uppose you have to code a class that calculates the factorial of an integer 4, which is the product of all integers from $ to 4. 7rite a failing test for it #do not code the class for now6 3nly the test. uppose that the class and its methods already exists 8ust like you want them to be.& $.+ 'dd more test methods, which try different input numbers: $, O, +%. Merify that different factorials are calculated correctly #again, 8ust the tests and no production code in this phase&. $./ 7rite the production code needed to make all tests pass.
'5
The test case class is named 'rray*teratorTest, following the convention of using a $:$ mapping from production classes to test ones. The test method simply creates a new instance of the system under test, setting up the situation to have it iterate over the empty array. *f the execution path enter the foreach, the test fails, as the call to fail#& is e.uivalent to assertTrue#false&. The next step is to cover other possible situations:
pu"lic #unction test=terates8ver8ne@lement<rrays%& ! 'iterator * new <rray=terator%array%B#ooB&&+ 'i * 5+ #oreach %'iterator as 'element& !
'6
, 'this-(assert@Cuals%): 'i&+
This test ensures that one!element numeric arrays are iterated correctly. The first assertion states that every element which is passed as the foreach argument is the element in the array, while the second that the foreach is executed only one time. 5ou have probably guessed that assert1.uals#& confronts its two arguments with the operator and fails if the result is fa"se. 7hen it is not too computational expensive, we should strive to have the few possible assertions per method; so we can separate the test method test*terates3ver3ne1lement'rrays#& in two distinct ones:
pu"lic #unction test=terates8ver8ne@lement<rraysUsin.Ealues%& ! 'iterator * new <rray=terator%array%B#ooB&&+ #oreach %'iterator as 'element& ! 'this-(assert@Cuals%B#ooB: 'element&+ , , pu"lic #unction test=terates8neTime8ver8ne@lement<rrays%& ! 'iterator * new <rray=terator%array%B#ooB&&+ 'i * 5+ #oreach %'iterator as 'element& ! 'iDD+ , 'this-(assert@Cuals%): 'i&+ ,
4ow the two test methods are nearly independent and can fail independently to provide information on two different broken behaviors: not using the array values and iterating more than one time on an element. This is a very simple case, but try to think of this example of a methodology to identify responsibilities of a production class: the test names should describe what features the class provides at a good level of specification #and they are really used for this purpose in 'gile documentation&. This is what we are doing by adopting descriptive test names and using a single assertion per test where it is possible: broken up the role of the class in tiny pieces which together give the full picture of the unit re.uirements. 7e can go further and test also the use of 'rray*terator on associative
'7
arrays:
pu"lic #unction test=terates8ver<ssociative<rrays%& ! 'iterator * new <rray=terator%array%B#ooB *( B"arB&&+ 'i * 5+ #oreach %'iterator as 'key *( 'element& ! 'iDD+ 'this-(assert@Cuals%B#ooB: 'key&+ 'this-(assert@Cuals%B"arB: 'element&+ , 'this-(assert@Cuals%): 'i&+ ,
's an exercise you can try to refine this method two three independent ones, for instance creating the first of them with a name such as test*terates3ver'ssociative'rrays0sing'rrayBeys's>oreachBeys#&. "on2t worry about long method names as long as they are long to strengthen the specification, but only when the code can be refactored to smaller test methods. 1ven then, finding descriptive test names is the most difficult part of the process. 7e can go on and add other test methods, and pl has many. 7henever a bug is found which you can impute to the class under test, you should add a test method which exposes the bug, and thus fails; then you can fix the class to make the test pass. This methodology helps to not reintroduce the bug in subse.uent changes to the class, as a regression test is in place. *t also defines more and more the behavior of a class by adding a method at the time. The T"" methodology not only forces to add test methods to expose bug, but also to define new features. *mplementing a user story is done by first writing a fail test which exposes the problem #the feature is not present at the time in the class& and then by implementing it. * hope you2re liking this 8ourney in testing and you2re considering to test extensively your code if you currently are not using phpunit or similar tools. *n the next chapter, we will make a panoramic the assertion methods which phpunit provides to simplify the tester work. Eemember that, in software unit testing, developer and tester coincide, or at least are at one next to the other, in the case of pair programming.
TDD exercises
+.$ 7hat does happen to your class from $.$ when you try to calculate
'&
the factorial of %: #it is assumed by definition e.ual to $.& 'dd a failing test case. +.+ (odify the production class to make all the tests pass.
'.
Code sample
FGphp class <rray=teratorTest extends PHPUnit_Framework_Test ase ! pu"lic function test@mpty<rray=s$ot=terated8ver%& ! 'iterator * new <rray=terator%array%&&+ foreach %'iterator as 'element& ! 'this-(#ail%&+ , , /H H separate this method in two distinct ones express "etter the intents o# H this test case pu"lic #unction test=terates8ver8ne@lement<rrays%& ! 'iterator * new <rray=terator%array%B#ooB&&+ 'i * 5+ #oreach %'iterator as 'element& ! 'this-(assert@Cuals%B#ooB: 'element&+ 'iDD+ , 'this-(assert@Cuals%): 'i&+ , H/ pu"lic function test=terates8ver8ne@lement<rraysUsin.Ealues%& ! 'iterator * new <rray=terator%array%B#ooB&&+ foreach %'iterator as 'element& ! 'this-(assert@Cuals%B#ooB: 'element&+ , , pu"lic function test=terates8neTime8ver8ne@lement<rrays%& ! 'iterator * new <rray=terator%array%B#ooB&&+ 'i * 5+ foreach %'iterator as 'element& ! 'iDD+ , 'this-(assert@Cuals%): 'i&+ , pu"lic function test=terates8ver<ssociative<rrays%& ! 'iterator * new <rray=terator%array%B#ooB *( B"arB&&+ 'i * 5+
2*
Chapter 2% #rite cle,er tests foreach %'iterator as 'key *( 'element& ! 'iDD+ 'this-(assert@Cuals%B#ooB: 'key&+ 'this-(assert@Cuals%B"arB: 'element&+ , 'this-(assert@Cuals%): 'i&+
, ,
2'
22
Chapter 3% assertions
from fa"se. assert!+uals'(expected# (actual) takes two arguments and confront them with the operator, declaring the test failed if they do not match. The canned result should be put in the Qexpected argument, while the result obtained from the system under test in the Qactual one: they will be shown in this order if the test fails, along with a comparison of the arguments dumps when applicable. assert4ot1.uals#& is this method2s opposite. assert,ame'(expected# (actual) does the identical 8ob of assert1.uals#&, but comparing the arguments with the operator, which checks also the e.uality of variable types along with their values. assertContains'(needle# (haystack) searches Qneedle in Qhaystack, which can be an array or an *terator implementation. assert4otContains#& can also be very handy. assert'rrayGasBey#Qkey, Qarray& evals if Qkey is in Qarray. *t is used for both numeric and associative ones. assertContains3nly#Qtype, Qhaystack& fails if Qhaystack contains element whose type differs from Qtype. Qtype is one of the possible result from gettype#&. assertType'(type# (variable) fails if Qvariable is not a Qtype. Qtype is specified as in assertContains3nly#&, or with PGP0nit types constants. assert4ot4ull#Qvariable& fails if Qvariable is the special value nu"".
assertFessThan#&, assert)reaterThan#&, assert)reatherThan3r1.ual#&, assertFessThan3r1.uals#& perform verifications on numbers and their names are probably self explanatory. They all take two arguments. assert trings tarts7ith#Qprefix, Qstring& and assert trings1nds7ith#Qsuffix, Qstring& are also self explanatory and section a string for you, avoiding the need for substr#& magic in a test. Eemember that you can still make up nearly any assertion by calling a verification method and pass the result to assertTrue#&. (oreover, nearly everyone of this methods support a supplemental string parameter named Qmessage, which will be shown in the case of a failing test caused by the assertion; if you2re making up a complex method for a custom assertion you may want to provide Qmessage to assertTrue#& to provide information in case the production code regress. 3bviously, the custom assertion
23
methods should be tested too. * think you will start soon to use the more expressive assertions for what you are testing for: test methods should be short and easily understandable, and assertion methods which abstract away the verification burden are very beneficial. *n the next parts, we2ll dig into ways to reuse test code and in the annotations which phpunit recogniLes to drive our test execution, such as #dataProvider and #depends.
TDD exercises
/.$ 7rite tests for a class which takes a single argument in the constructor and gives it back when a getter is called #assert$ame%& or assert'(ua"s%&:&, then write the production class. /.+ 7rite tests for the sort#& php function, for simplicity with integer arrays as data.
2-
Chapter 3% assertions
Code sample
FGphp class <ssertionsTest extends PHPUnit_Framework_Test ase ! pu"lic function test<ssertionsMethodsIorksIith orrect<r.uments%& ! 'this-(assertTrue%) ** )&+ 'this-(assertTrue%true&+ 'this-(assertFalse%#alse&+ 'this-(assertFalse%5 ** )&+ 'this-(assert@Cuals%B#ooB: B#ooB&+ 'this-(assert@Cuals%): J)J&+ 'this-(assert$ot@Cuals%): K&+ 'this-(assert6ame%): )&+ 'this-(assert$ot6ame%): J)J&+ 'this-(assert ontains%B#ooB: array%B#ooB: BotherEalueB&&+ 'this-(assert ontains%B#ooB: new <rray=terator%array%B#ooB: BotherEalueB&&&+ 'this-(assert$ot ontains%B#ooB: array%BotherEalueB&&+ 'this-(assert ontains8nly%Bstrin.B: array%B#ooB: BotherEalueB&&+ 'this-(assert<rrayHas9ey%5: array%B#ooB&&+ 'this-(assert<rrayHas9ey%B#ooB: array%B#ooB *( B"arB: BotherEalueB&&+ , ,
25
Chapter #: fi$tures
*n the previous parts, we have explored how to install phpunit and how to write tests which exercise our production code. 'lso we have learned to use the assertion methods to check the actual results: now we are ready to improve the test code from a refactoring point of view, and to take advantage of phpunit features. 7hile writing more and more test methods, you can notice that you follow a common pattern, commonly known as arrange)act)assert; this is the main motif of state based testing. The first operation in a test is usually to set up the system under test, being it an ob8ect or a complex ob8ect graph; then the methods of the ob8ect are called during the act part and some assertions #hopefully not more than one& are done on the results returned from these calls. *n some cases, when you have allocated external resources like a fake database, a final cleaning up phase is needed. 7hat you will actually discover is that often part of the arrange phase and the final cleanup code are shared between test methods: for example in case you are testing a single class, the instantiation of an ob8ect is a simple operation you can extract from the test methods. To support this extraction, phpunit #and all x0nit frameworks& provide the set0p#& and tear"own#& template methods. These methods are executed respectively before and after every test method: default implementations are provided in PGP0nit<>ramework<TestCase with an empty body. 5ou can override this empty methods when useful, to have arrange9cleanup code to be shared between tests in the same test case and prepare a known state before every run. This known state is called a fi*ture. 5our test case class can go from this:
FGphp class <rray=teratorTest extends PHPUnit_Framework_Test ase ! pu"lic #unction test6omethin.%& ! 'iterator * new <rray=terator%array%BaB: B"B: BcB&&+ // act: assert... , pu"lic #unction test8therFeature%&
26 !
Chapter -% $i+tures
to this:
FGphp class <rray=teratorwithFixtureTest extends PHPUnit_Framework_Test ase ! private '_iterator+ pu"lic #unction setUp%& ! 'this-(_iterator * new <rray=terator%array%BaB: B"B: BcB&&+ , pu"lic #unction test6omethin.%& ! // act on 'this-(_iterator: assert... , pu"lic #unction test8therFeature%& ! // act on 'this-(_iterator: assert... , ,
3bserve that, since an ob8ect of this class will be created to run the test, you can conserve every variable you want as a private member, and then have a reference to it available in the test method. set0p#& usage provides a cleaner and dont)repeat)yourse"f solution, and saves many lines of code when many test methods are needed. Gere is some know!how on using fixtures: usually the tearDown') method should not be provided since the fixture is an ob8ect graph and will be garbage!collected after all the tests are executed, or overwritten by the next set0p#& call. Thus, the empty body provided by default is often enough. the fixture methods are executed for every test, so the test methods have the same state as a starting point. 7hen more than one fixture is re.uested, the common practice is to break down the test case, preparing more than one test case class for the system under test; these classes represents different scenarios and
27
together constitutes the overall test suite for this system. sharing a fixture between test cases can be a smell for a bad design, since they are not insulated enough and classes know too much of each other. This cannot be done with set0p#& methods however, but there are suite!level setup available in phpunit if you must share a fixture. Gowever, keep in mind that you probably can refactor your classes to improve the maintainability of the application and of its test suite. set p-eforeClass') and tearDown$fterClass') are two hooks #static methods& which are executed before a test case methods are considered and after the overall process is finished. They are the e.uivalent of set0p#& and tear"own#&, but at the test case level instead of the test method one. finally, assert.reConditions') and assert.ostConditions') are two methods executed before and after the a test method. They differ from set0p#& and tear"own#& since they are executed only if the test did not already fail and they can halt the execution with a failing assertion. set0p#& and tear"own#& should never throw exceptions and they are executed anyway, unconcerned by the current test outcome. This is all you must know on test fixtures to start experimenting with them. * hope your test code will be much more well written after introducing set0p#&. *n the next chapter, we2ll explore the annotations that can influence phpunit test runner, like Rdepends and RdataProvider.
TDD exercises
O.$ 7rite a class orter that wraps the sort#& native function and returns an ordered integer array without touching the original. tart with some tests before writing a single line of production code. O.+ Gow many new do you have in the test case: Eefactor till you have only one ob8ect creation.
2&
Chapter 5% annotations
Chapter %: annotations
4ow that we have learned much about writing tests #with or without fixtures& and using assertions, we can improve our tests further by exploiting phpunit features. This awesome testing tool provides support for several annotations which can add behavior to your test case class without making you write boilerplate code. 'nnotations are a standard way to add metadata to code entities, such as classes or methods, and are composed by a R tag followed by Lero, one or more arguments. 7hile the parsing implementation is left to the tool which will use them, their aspect is consistent: php"ocumentor also collects Rparam and Rreturn annotations to describe an 'pi. Eemember that annotations must be placed in docblock comments as in the php engine there is no native support for them: phpunit extracts them from the comment block using reflection. 7hile writing an 'pi or also a simple class, the corner cases and incorrect inputs have to be taken into consideration. The standard way to manage errors and bad situations in an oop application is to use exceptions. @ut how to test that a method raises an exception when needed: 3f course the normal behavior is tested with real data that returns a real result. >or the exceptional behavior, we can start with this test method:
pu"lic #unction testMethod?aise@xception%& ! try ! 'iterator * new <rray=terator%4K&+ 'this-(#ail%&+ , catch %=nvalid<r.ument@xception 'e& ! , ,
The purpose of this code is to raise an exception by passing invalid data to the constructor of 'rray*terator, which re.uires an array. *f the exception is raised accordingly, it bubbles up to the end of the try block and it is catched correctly, making the test pass. *f the exception is not thrown, the call to fail#& declares the test failed. Gowever, this paradigm will be repeated very often everytime you need to test an exception and so it can be abstracted away. 'lso, this code does
2.
not convey the intent of testing an exception since it is cluttered with details like an empty catch block and calls to fail#&. Phpunit already abstracts away this code providing an annotation, /expected!xception, which has to be put in the method docblock:
/HH H /expected@xception =nvalid<r.ument@xception H/ pu"lic #unction testMethod?aise@xception<.ain%& ! 'iterator * new <rray=terator%4K&+ ,
This code is much more clear than the constructs we used earlier. The only code present in the method is the one re.uired to throw the exception, while the intent is described in the method name and in its annotations. 'nother common repetition is testing a method with different kind of inputs, while executing always the same code. This is commonly resolved with a loop:
pu"lic #unction test7oolean@valuation=n<>oop%& ! 'values * array%): B)B: BonB: true&+ #oreach %'values as 'value& ! 'actual * %"ool& 'value+ 'this-(assertTrue%'actual&+ , ,
@ut phpunit can do the loop for you, taking advantage of the RdataProvider annotation:
pu"lic static #unction trueEalues%& ! return array% array%)&: array%B)B&: array%BonB&: array%true& &+ , /HH H /dataProvider trueEalues H/ pu"lic #unction test7oolean@valuation%'value& ! 'actual * %"ool& 'value+ 'this-(assertTrue%'actual&+ ,
3*
Chapter 5% annotations
This annotation should be followed by the name of a static method in the test case which returns an array of data sets to be passed to the test method. Phpunit will iterate over this array and using each one of its elements #which will be an array containing the arguments& to run the test method, telling you which data set was in use in case of a test failure. 3f course you can put anything in the data sets: input for the 0T or expected result, or both. The code becomes a bit longer, but the expressivity of defining the concept of different data sets in a standard way are worth considering. The last common situation we will look at today is test dependency. 'gain, we are talking about dependency beneath the same test case since interdependencies between unit tests are a small of high coupling and should be raise suspects about your classes design. *t happens often that some test methods are more specific than the first you wrote and they will obviously fail if the formers do. The classic example is the add#&9remove#& tests on a container: to make sure remove#& works you have to use add#& for the arrange part of the test method. Phpunit solve this common problem of logic and temporal precedence #* won2t present a workaround like in the other cases since it was not possible to solve this issue before phpunit /.O introduced Rdepends&:
pu"lic #unction test<rray<dditionIorks%& ! 'array * array%&+ 'array-52 * B#ooB+ 'this-(assertTrue%isset%'array-52&&+ return 'array+ , /HH H /depends test<rray<dditionIorks H/ pu"lic #unction test<rray?emovalIorks%'#ixture& ! unset%'#ixture-52&+ 'this-(assertFalse%isset%'#ixture-52&&+ ,
4ot only test'rray'ddition7orks#& is executed before test'rrayEemoval7orks#&, but since it returns something, this result is passed as an argument to the dependent method. *f the former test fails, however, the dependent ones are marked as skipped as they will fail anyway by definition. They will clutter the output too, while it is clear that the functionality that needs repairment is the array addition.
3'
* hope this standard phpunit annotations can help you en8oy writing tests for your php classes, leaving you the exciting work and taking off the boring one. *n the next parts, we2ll look at refactoring for test code before taking a 8ourney with stubs and mocks.
TDD exercises
S.$ Eefactor the tests from O.+ using the RdataProvider annotation. S.+ 7rite tests for a Collection class which stores ob8ects, and has the methods add#&, contains#& and remove#&. 7here you should put Rdepends annotations: S.+ *mplement the Collection class.
32
Chapter 5% annotations
Code sample
FGphp class <nnotationsTest extends PHPUnit_Framework_Test ase ! pu"lic function testMethod?aise@xception%& ! try ! 'iterator * new <rray=terator%4K&+ 'this-(#ail%&+ , catch %=nvalid<r.ument@xception 'e& ! , , /** H /expected@xception =nvalid<r.ument@xception H/ pu"lic function testMethod?aise@xception<.ain%& ! 'iterator * new <rray=terator%4K&+ , pu"lic function test7oolean@valuation=n<>oop%& ! 'values * array%): B)B: BonB: true&+ foreach %'values as 'value& ! 'actual * %"ool& 'value+ 'this-(assertTrue%'actual&+ , , pu"lic static function trueEalues%& ! return array% array%)&: array%B)B&: array%BonB&: array%true& &+ , /** H /dataProvider trueEalues H/ pu"lic function test7oolean@valuation%'value& ! 'actual * %"ool& 'value+ 'this-(assertTrue%'actual&+ , pu"lic function test<rray<dditionIorks%& !
Practical Php Testing 'array * array%&+ 'array-52 * B#ooB+ 'this-(assertTrue%isset%'array-52&&+ return 'array+
33
3-
35
obviously it should have no state or a way to reach a particular state for testing purposes. This pattern can be implemented with phpunit set0p@eforeClass#& method. *our .hase Test is the classical motif of a test method: arrange, act, assert and the optional teardown. Test 0unner and Test ,uite 1b%ect are pattern which phpunit implements for you. 5ou can then specifiy metadata to alter the building of a test suite or execution options, or specifical annotations which the runner supports. ,tate 2erification is the simplest way of using phpunit and it2s what we have done until now, writing assertions on explicit results of the system under test. -ehavior 2erification is based on making assertiong on indirect results, like method calls on collaborators and will be treated in the next chapters; 3ock and ,tub are patterns used in @ehavior Merification, and phpunit provides support for their dynamic creation. Table Truncation Teardown and Transaction 0oll-ack TearDown are standard patterns for testing components which interact with a database. 4iteral# Derived and 5enerated 2alue are patterns to provide fake data to the system under test. They all have their place in unit testing, depending on the unit purpose. *f you are interested in learning more about patterns you should check out the book x0nit Test Patterns: Eefactoring Test Code and its website #http:99 xunitpatterns.com9index.html&, which is a very complete guide to probably every single testing construct that has been explored in the x0nit frameworks so far. 3n the website you can find description and usage examples of all the patterns described here and of other specific ones. (oreover, remember that test code is still code and the basic refactorings like 1xtract (ethod, 1xtract uperclass, *ntroduce 1xplaining Mariable etc. are valid also in the testing land. imply refactor some boilerplate code in private methods of a test case can save you the boring 8ob of updating duplicated blocks. 's a side note, remember that when refactoring production code you have the safety net of the test suite, that will tell you when you have 8ust broke something. 4o one tests the tests, however, and so you may want to temporarily break the behavior under test before refactoring a method or a test case. imply altering the return statements of
36
production methods can make the test fail so you can control that it continue to fail after the refactoring. 7hen writing the original test, the T"" methodology crafts the method even before the production code exists and this is one of the main reason why the test is solid; a test is valid if it2s able to find problems in the production code: that is, failing when it should fail. * hope this book is becoming interesting as now you have learned your tests have the same importance of the production code. They can even be more important: if * have to choose between throwing away the production code and its documentation, and losing a good test suite, * will definitely throw away the first. *t can be rewritten using the tests, while writing a complete test suite of an application without any tests is an harder task. *n the next parts, we2ll enter the world of Test "oubles and of behavior verification, taking advantage of (ocks, tubs, >akes and similar patterns.
TDD exercises
T.$ Eefactor the tests you have written in the previous parts to eliminate duplication, introducing creation methods and ensuring the four9three phases of tests are identifiable. T.+ 0se the Pdo .lite driver to implement Table Truncation Tear"own for a small test database #one table will suffice&. 5ou should use the tear"own#& method to empty the table.
37
3&
Chapter 7% stubs
should definitely instantiate 0ser and )roup in tests of service classes which acts on them. The verb su+stitute is appropriate: the only way to keep out collaborators from a unit test is to replace them with other ob8ects. These ob8ects are commonly known as Test "oubles and they are subclasses or alternative implementations respectively of the class or interface re.uired by the 0T. This property allows them to be considered instanceof the original collaborator class9interface and to be kept as a field reference or to be passed as a method parameter, which are the only ways * can think of to recogniLe a collaborator. "ependency *n8ection plays a key role in many unit tests: in the case of a field reference on a class, there is the need to replace this reference with the Test "ouble one, to hi8ack the method calls on the collaborator itself. ince field references are usually private, it is difficult to access them #re.uiring reflection& and violating a class encapsulation to simplify testing does not seem a good idea. Thus, the natural way to provide Test Doubles for collaborators is Dependency "n%ection, being it implemented via the constructor or via setters. *nstead of instantiating the production class, simply produce an ob8ect of its custom!made subclass and use it during the construction of the 0T. (y 0Ts usually have optional constructor parameters to allow passing in a null when the collaborator is not needed at all. 7hile entering the Test "oubles world, the average programmer hears many terms which describes substitutes of an ob8ect2s collaborators. *n crescent order of complexity, they are: Dummies: ob8ect which do not provide any interaction, but are placeholders used to satisfy compile!time dependencies and to avoid breaking the null checks: no method is called on them but they are likely to be re.uired parameters of a 0T method or part of its constructor signature. To avoid the creation of dummies * prefer to keep all constructor parameters optional, trusting that the >actory which creates the ob8ect passes regular collaborators instead of null values. ,tubs: ob8ects where some methods have been overridden to return canned results. They may have more than one precalculated result available, depending on the method parameters combination, but these variables are known values once you reach the act part of the test method.
3.
3ock ob%ects: also known as Test pies, mock ob8ects are used in a testing style different from what we have worked on since the first chapter of this book #behavior based testing&. They will be the main argument of the next episode. *akes: ob8ects which have a working implementation, but much simpler than the real collaborator one. 'n in!memory 'rray3b8ect which substitutes a database result *terator is an example of a >ake. 5ou generally don2t need to write a "ummy ob8ect since there is no interaction with it: the real collaborator can be used instead if its constructor is thin. ' >ake is a running implementation so if an already existing class cannot work, there2s usually no other choice than write a real subclass and reusing it in all the tests that re.uire the collaborator. >or tubs and (ocks the situation is different: there are plenty of frameworks for nearly every language which provide help in generating them, which take care of evaluating the subclass code and instantiating an ob8ect. .hpunit incorporates a small mocking framework, accessible through the method get(ock#& on the test case class. Eemember that while the method is named get(ock#&, both tubs and (ocks can be created via this 'pi. *n this chapter we2ll focus again on state verification and we2ll use a tub to improve the granularity of a test. 7e are going to give a meaningful example of unit testing using a ,tub. *n this example, a )eolocation ervice takes a 0ser ob8ect and fills its latitude and longitude fields using the location specified. )eolocation ervice re.uires an instance of a fictional )oogle(aps ob8ect to work, and since we all love "ependency *n8ection it is passed in its constructor. 4ote that )oogle(aps can also be an interface or a base abstract class: there is no technical difference. (oreover, if it was a less important collaborator it can even be passed as a locate#& parameter. This is the test case:
class Leolocation6erviceTest extends PHPUnit_Framework_Test ase ! pu"lic #unction testProvides>atitude<nd>on.itudeFor<nUser%& ! 'coordinates * array%BlatitudeB *( B4K$B: Blon.itudeB *( B)K@B&+ '.oo.leMapsMock * 'this-(.etMock%BLoo.leMapsB: array%B.et>atitude<nd>on.itudeB&&+ '.oo.leMapsMock-(expects%'this-(any%&& -(method%B.et>atitude<nd>on.itudeB& -(will%'this-(returnEalue%'coordinates&&+
-*
Chapter 7% stubs 'service * new Leolocation6ervice%'.oo.leMapsMock&+ 'user * new User+ 'user-(location * B?omeB+ 'service-(locate%'user&+ 'this-(assert@Cuals%B4K$B: 'user-(latitude&+ 'this-(assert@Cuals%B)K@B: 'user-(lon.itude&+
, ,
4ote that * have created a tub only for the external service and not for the 0ser class. The former is external, slow, and unpredictable, but the latter is simple, with little or none internal behavior, and there2s a small chance it will break. ,othing +ehaves "ike a $tring more than a $tring, as (isko Gevery says. The test method now focuses on exercising the locate#& method and not also the )oogle(aps class. >inally, let2s take a look at the get3ock') $pi:
o"Mect .etMock%'ori.inal lass$ame: -array 'methods: -array 'ar.uments: -strin. 'mock lass$ame: -"oolean 'call8ri.inal onstructor: -"oolean 'call8ri.inal lone: -"oolean 'call<utoload222222&
QoriginalClass4ame is the name of the class you want to create a tub9(ock for. Qmethods is a numeric indexed array containing the names of the methods you want to subclass; if left empty, every method will be substituted. Qarguments are arguments to pass to the constructor of the original class #almost never used&, while QmockClass4ame is a custom name for the subclass created. The last three arguments are boolean values used to determine if leaving the original constructor or clone method, or to allow autoloading of QoriginalClass4ame. They default to true. 3ften you want to set Qcall3riginalConstructor to false if its signature re.uires other collaborators to be passed in. 'll arguments but the first one are optional. The mock produced, along with the original class methods, also has the expects#& one available. >or now, simply calling it with the argument Qthis! Uany#& will do the 8ob. This method returns a PGP0nit<>ramework<(ock3b8ect<@uilder<*nvocation(ocker instance; in short, an internal ob8ect that you can call method#& and will#& on to decide the method name to replace and its predefined behavior. The simplest possible behavior is Qthis!UreturnMalue#...&, but also Qthis! Ureturn'rgument#Qargument4umber& is available, along with Qthis!
-'
UreturnCallback#Qcallback4ame&; refer to the phpunit documentation for supplemental informations. * hope this introduction to tub ob8ects has helped you grasping the essence of 0nit testing in php. >eel free to ask clarifications in the comments. *n the next chapter of this book, we will explore the possibilities of (ock ob8ects and behavior based testing.
TDD exercises
?.$ 7rite an infinite >ibonacci*terator that returns the >ibonacci series and test it. The >ibonacci series is %, $, $, +, /, S, V... every term is the sum of the two previous ones. ?.+ 7rite a 1ven*terator which takes a >ibonacci*terator an iterates only on the even!indexed values #returning %, $, /, V, +$...&. ?./ 7rite tests for the 1ven*terator class, stubbing out the >ibonacci*terator using an 'rray*terator in substitution, which is provided by the pl #otherwise it will never terminate6& ?.O 7rite a class that uses GTTP<Client Pear package to check a list links and find out which are broken #O%O error&. tart with testing it without instancing the real GTTP<Client class by stubbing it.
-2
Chapter 7% stubs
Code sample
FGphp /HH H For "revity = use pu"lic #ields instead o# .etters and setters. H/ class User ! pu"lic 'location+ pu"lic 'latitude+ pu"lic 'lon.itude+ , class Loo.leMaps ! pu"lic function .et>atitude<nd>on.itude%'location& ! // some obscure network code to contact maps.google.com // ... return array%BlatitudeB *( 'someEalue: Blon.itudeB *( 'some8therEalue&+ , , class Leolocation6ervice ! private '_maps+ pu"lic function __construct%Loo.leMaps 'maps& ! 'this-(_maps * 'maps+ , pu"lic function locate%User 'user& ! 'location * 'user-(location+ if %'location *** null& ! 'location * BMilanB+ , 'coordinates * 'this-(_maps(.et>atitude<nd>on.itude%'location&+ 'user-(latitude * 'coordinates-BlatitudeB2+ 'user-(lon.itude * 'coordinates-Blon.itudeB2+ , , class Leolocation6erviceTest extends PHPUnit_Framework_Test ase ! pu"lic function testProvides>atitude<nd>on.itudeFor<nUser%& ! 'coordinates * array%BlatitudeB *( B4K$B: Blon.itudeB *(
Practical Php Testing B)K@B&+ '.oo.leMapsMock * 'this-(.etMock%BLoo.leMapsB: array%B.et>atitude<nd>on.itudeB&&+ '.oo.leMapsMock-(expects%'this-(any%&& -(method%B.et>atitude<nd>on.itudeB& -(will%'this-(returnEalue%'coordinates&&+ 'service * new Leolocation6ervice%'.oo.leMapsMock&+ 'user * new User+ 'user-(location * B?omeB+ 'service-(locate%'user&+ 'this-(assert@Cuals%B4K$B: 'user-(latitude&+ 'this-(assert@Cuals%B)K@B: 'user-(lon.itude&+ , ,
-3
--
Chapter (: moc)s
*n the previous chapter of this book, we have listed the various types of Test "oubles along with the ones that phpunit can easily generate: tubs and (ocks. The latter are utiliLed in a different kind of testing than the one presented so far: behavior verification. The behavior verification testing style differs from the state verification one in the sub8ects of the assertion methods. 7hile state verification specifies explicit assertion methods to be called upon a test result, behavior verification is focused on checking the actions the system under test undertakes. These actions comprehend which methods it calls, and how many times it does so; but also the parameters it passes to these methods and their order. The standard interaction with collaborators in ob8ect!oriented systems consists of method calls. This kind of testing prescribes to place assertions directly in the overridden methods of Test "oubles, or at the end of every test, to verify that the 0T behavior conforms to specifical rules. These Test "oubles, which can run assertions on their methods parameters, are called (ock 3b8ects #or simply (ocks&. The contraposition here is with tubs, which extend the capabilities of a state based testing but do not make any assumption on method calls or parameters. 4ote that the assertions on parameters are placed inside the generated methods, while assertions on method calls are executed by phpunit after the test has run. This means that in a pure behavior verification test you won2t find any assertP#& calls, which perform state verification. 4ow we are going to rewrite the unit test of the previous chapter taking advantage of phpunit mocks generation, but with a mixed approach which contains also explicit assertions. The test was about verifying that the )eolocation ervice class made use of a )oogle(aps collaborator to find out the latitude and longitude of an 0ser ob8ect, and the key characteristic was insulation of the test from the )oogle(aps real implementation with a Test "ouble. 5ou can find the tub example at the end of the previous chapter.
class Leolocation6erviceIithMocksTest extends
Practical Php Testing PHPUnit_Framework_Test ase ! pu"lic #unction testProvides>atitude<nd>on.itudeFor<nUser%& ! 'coordinates * array%BlatitudeB *( B4K$B: Blon.itudeB *( B)K@B&+ '.oo.leMapsMock * 'this-(.etMock%BLoo.leMapsB: array%B.et>atitude<nd>on.itudeB&&+ '.oo.leMapsMock-(expects%'this-(once%&& -(method%B.et>atitude<nd>on.itudeB& -(with%B?omeB& -(will%'this-(returnEalue%'coordinates&&+ 'service * new Leolocation6ervice%'.oo.leMapsMock&+ 'user * new User+ 'user-(location * B?omeB+ 'service-(locate%'user&+ 'this-(assert@Cuals%B4K$B: 'user-(latitude&+ 'this-(assert@Cuals%B)K@B: 'user-(lon.itude&+ , ,
-5
The test is actually very similar to the tub one, but there are some differences: the expect#& method of the mock returns an expectation ob8ect with a fluent interface we can work with. Gowever, this time a matcher is passed which specifies how many times the mocked method should be called. *n the tub example, the matcher used is Qthis!Uany#&, that does not run any assertions on the number of calls at the end of the test. 3ther available matchers are Qthis! Unever#& and Qthis!Uexactly#Qnumber&. The power of the matchers used in x0nit frameworks is they augment the test2s code readability, making it similar to plain 1nglish. 3n the expectation ob8ect, along with will#& and method#&, we are also calling with') to specify the parameter we want to check as passed to getFatitude'ndFongitude#&. *f we wanted to check more parameters as exact values, we would pass an array to with#& containing the actual list. Gowever, we can make also weak assertions by using constraints ob8ects, like Qthis! Uattribute1.ualTo#Qname, Qvalue& or Qthis! Uis*nstance3f#Qclass4ame&, or maybe Qthis!Uanything#& if no assertion has to be made on a particular parameter. There is no formal definition that says (ocks can2t return canned results, as this is often mandatory for the code flow and to complete the test successfully. Though, if you T"" the system under test
-6
using mocks without predefined results, it2s likely that you will produce a class with a different programming style which works with those tests, and uses mocks very effectively. 7henever you write a with#& call or a matcher in expects#&, be aware you are building a 3ock and not a tub. 5ou can find the complete, running test case at the end of the chapter. * tried to include complete examples in this book to show the practical side of testing instead of tips which are great in theory, but fail to apply in a real situation. 'fter this example of behavior verification, which makes use of the most advanced phpunit features, we are ready to explore the code coverage features in the next chapter.
TDD exercises
V.$ 7rite tests for a class PermissionEeader that has a <<to tring#& method which produces a human readable string, containing the permissions of a pl>ile*nfo which is passed to it in the constructor. 4o actual file should be used, only mocks #you can use actual files to learn about pl api but they should not be present in the final production code and unit tests&.
-7
Code sample
FGphp /HH H For "revity = use pu"lic #ields instead o# .etters and setters. H/ class User ! pu"lic 'location+ pu"lic 'latitude+ pu"lic 'lon.itude+ , class Loo.leMaps ! pu"lic function .et>atitude<nd>on.itude%'location& ! // some obscure network code to contact maps.google.com // ... return array%BlatitudeB *( 'someEalue: Blon.itudeB *( 'some8therEalue&+ , , class Leolocation6ervice ! private '_maps+ pu"lic function __construct%Loo.leMaps 'maps& ! 'this-(_maps * 'maps+ , pu"lic function locate%User 'user& ! 'location * 'user-(location+ if %'location *** null& ! 'location * BMilanB+ , 'coordinates * 'this-(_maps(.et>atitude<nd>on.itude%'location&+ 'user-(latitude * 'coordinates-BlatitudeB2+ 'user-(lon.itude * 'coordinates-Blon.itudeB2+ , , class Leolocation6erviceIithMocksTest extends PHPUnit_Framework_Test ase ! pu"lic function testProvides>atitude<nd>on.itudeFor<nUser%& !
-&
Chapter &% moc"s 'coordinates * array%BlatitudeB *( B4K$B: Blon.itudeB *( B)K@B&+ '.oo.leMapsMock * 'this-(.etMock%BLoo.leMapsB: array%B.et>atitude<nd>on.itudeB&&+ '.oo.leMapsMock-(expects%'this-(once%&& -(method%B.et>atitude<nd>on.itudeB& -(with%B?omeB& -(will%'this-(returnEalue%'coordinates&&+ 'service * new Leolocation6ervice%'.oo.leMapsMock&+ 'user * new User+ 'user-(location * B?omeB+ 'service-(locate%'user&+ 'this-(assert@Cuals%B4K$B: 'user-(latitude&+ 'this-(assert@Cuals%B)K@B: 'user-(lon.itude&+ , ,
-.
Eunning phpunit with this switch, instead of specifying a particular file, will force the runner to consider all php files which name ends in XTest.phpX in the directories tests9 and application9modules9. 7hile running a single test case gives as output a line of dots, running all these tests in se.uence will result in multiple lines and in a list of all failed tests #although you can re.uire the list of skipped and incomplete tests by using the &&verbose
5*
switch& in the overall list generated according to the configuration. 'long with the !!configuration option, * strongly suggest to use the &&bootstrap (php,cript directive. 5our test cases probably need a global bootstrap phase for autoloading and setting up the include<path or other options. *n some old versions of phpunit, you had to include a re.uire<once#& call at the start of each test script to make sure it was executed before the test. 4ow you can simply tell phpunit to run a file of your choice before starting with the test phase. Eunning an entire suite is a good practice to discover if your changes or refactorings have broken some functionalities. Gowever, it2s an overkill if you have to do it very often, like in a short feedback cycle for T"": supposing you have more than one test case for your 0T, it can be useful to select all those tests and leaving out the rest of the suite. This is the case when the /group annotation is handy. 5ou can mark with the /group (name annotation the docblock of test case classes, and also add multiple lines if you feel the contained tests can be useful in more than one scenario. Then the &&group (group command line switch excludes test cases which do not belong to Qgroup from being run. o we can finally give an example of running a test suite:
phpunit --"ootstrap tests/TestHelper.php --con#i.uration*tests/con#i.uration.xml
for instance we can restrict the selected tests to the 4akedPhp<>orm package ones:
phpunit --"ootstrap tests/TestHelper.php --con#i.uration*tests/con#i.uration.xml --.roup*$akedPhp_Form
or re.uiring a code coverage report to see where we need to add test code:
phpunit --"ootstrap tests/TestHelper.php --con#i.uration*tests/con#i.uration.xml --covera.e-html directory/
* hope these tips will be useful to you for utiliLing phpunit at its best. *t is a very well!crafted tool that you can take advantage of for T"" purposes, and also for functional and integration tests. 'lthough the name suggests unit testing as a goal, you should certainly include in your test suite some functional tests, which exercise a feature provided of more than one
5'
ob8ect, and integration tests, which covers the wiring of your ob8ect and verify that your application works on an end!to!end basis. -onus tip. using --no-globals-backup and --no-static-backup can speed up your tests e*ecution +y avoiding unusefu" iso"ation of tests. /f your app"ication has no g"o+a" state they wi"" work correct"y anyway.
TDD exercises
,.$ Eun all your tests and generate an html coverage report. *f you have T""ed your classes, the coverage should be close to $%%D.
52
TDD Phases
The basic T"" cycle #around five minutes& consists in three phases. Eed and green are the typical coloriLation of W0nit results.
RED
5ou write a failing test. This test should be exercising one feature of your 0T, which does not exist yet. The more fine!grained the test is, the
53
shorter the cycle will become and there will be less chances to breaking up working code or wondering on how to make the tests pass. 4ote that the test should fail, to ensure that it actually tests the 0T. ince the 0T or the particular 0T2s functionality does not exist yet, it is obvious that a correct test will fail. ometimes you craft a passing test to improve coverage or that accidentally passes since your code already implements the functionality. There are different reactions in this case: if the 0T already implements correctly the functionality you intend to test, leave the test in place and restart the cycle. Probably you will add this type of tests to gain confidence before ma8or refactorings. if the 0T does not implement the full functionality, try to change the input to catch the 0T when it is failing. (aybe it does return a correct result for this data by accident.
GREEN
7hen you have a shiny new failing test, you should now open the 0T2s class file#s& and start writing production code. 5ou are allowed to write as much production code as it is needed to make the test pass. 5ou2re not allowed to write more code. *f * can remove a line of code from your 0T and still get all tests green, this means this line is not necessary #so remove it yourself before * do&. This design choice is done to prevent bloat and, more importantly, to not allow untested code to leak into the 0T.
5-
The reason of this rule!of!thumb is that once you start breaking the code it becomes difficult to going back. The best solution is to maintain a working application all the time. 5ou can also refactor tests: in this case you should work during a red phase, even by temporarily breaking the 0T, to test that the tests really fail when they should do #no pun intendedY to watch the watchmen&. (ore often, you will refactor tests and production code at the same time to better assign responsibilities. Try to keep a working system for as long as you can, or you can find yourself in a situation where you cannot get a green state anymore and you are forced to revert to the initial version. 'fter Zpotential[ refactoring, go back to red and start writing another test. 4ow you are an expert on T"" theory and you have a reference guide here. The next step is practicing# practicing and practicing. *t is said that to become an expert T""er you should write at least $S%% tests, so you2d better off starting now. :&
55
56
7hile we should strive for decoupling between production classes, the bond between unit tests and their 0Ts is forced to be strong. The more we move to an higher view #functional and integration tests&, and the more we abstract away to simplify maintenance. *ntegration and acceptance tests are usually black!box tests which do not care about how we produce a value #how the s.uare root function is calculated or an html page is generated&, but only about the final result. 0nit tests instead are focused on ensuring that a unit of functionality works well, and are written to drive design of the 0T. 7hen we read code coverage reports, we tune unit tests to cover more code paths #but if you do T"" code coverage is always very high&. *f!else branches, while conditions and earlier returns are exercised by different white!box tests. *f we had the implementation of s.rt#& in userland code, we should code different tests that exercise different code branches. @etter, we should write different tests and extend the code 8ust enough to make these tests pass. There is an e.uivalence relation between input values that stimulate the 0T in the same way #run the same code&. 7e should include in white!box tests only some values from each e.uivalence class, usually including the boundaries of every range if we are dealing with numeric inputs. Though, we are limited to black box testing in this case since s.rt#& is implemented in C, which does not fall under the umbrella of our code coverage tools. 4ote that if we T"" the class, we are doing white box testing and every new test method or input value we add fail by definition #red phase&. This means that the new code that is added is exercised only by this input and state combinations and was not covered by previous tests. Then the test passes #green&, and the cycle repeats with new white!box tests that do not overlap. o T"" as predicted is a great method for writing unit tests. *n the orange phase, we refactor code and it can happen that different tests cover the same code after refactoring. *n this case, we can indeed extend refactoring to the tests themselves, but test abundancy is rarely a problem in real world #test performance is& and a long test suite is a safety net for future refactoring, as long as it runs sufficiently fast.
57
(y black box test case for s.rt#& is at the end of the chapter, while a white box test case would be the final product of the exercise.
TDD Exercises
$$.$ T"" a Calculator class, which has only the method Calculator::s.rt#&. *mplement the class one test at the time. Eemember that: ! you should add a PfailingP test and then improving production code to handle the new test case. ! you can add a test only if the tests are green ! you can add production code only if the tests are red, and if * removed part of your addition the test should return red #ensuring you are not writing unused code& ! you cannot use the s+rt') function in this exercise ! you can use a guess!and!check method: to find the s.uare root of /T, try $, try +, try /... until you get to T. Eound the result to the nearest integer.
5&
Code sample
FGphp class 6Crt7lackTest extends PHPUnit_Framework_Test ase ! pu"lic function input$um"ers%& ! return array% array%5: 5&: array%): )&: array%4: K&: array%N: 3&: array%-): $<$&: array%-K: $<$&: array%)555555: )555&: &+ , /** H /dataProvider input$um"ers H/ pu"lic function test6Cuare?oot=s alculated%'input: 'output& ! 'this-(assert@Cuals%'output: sqrt%'input&&+ ,
5.
0lossary
$pi: interface that a software program implements in order to allow other software to interact with it. *n ob8ect!oriented programming, an 'pi is composed of interfaces and final classes which the client program depends on and that are provided by the original program, which maybe a library or a framework like Aend >ramework or PGP0nit. constraint ob%ect: representation of a specification about ob8ects. >or instance, a *s1.ual constraint ob8ect in PGP0nit can be used to check other ob8ect are e.ual to the one passed at construction. *n """ this type of ob8ect is called a pecification pattern and it is the personification #or ob8ectification& of an selection criteria: the more you think in ob8ects, the more reusable the resulting code will be. 4aw of Demeter: 3nly talking to immediate ob8ect references, often stated as using on"y one dot or 1 in every "ine of code. The purpose of this law #actually a suggestion& is to protect a class from changes in far collaborators, by accessing only its immediate friends from its code. DDD aka Domain&Driven Design: a style of development that focus in domain knowledge, where the core of an application consists in the classes and ob8ects that represents the domain and everything else depends on them. dependency in%ection: the process of supplying an external dependency to a software component, instead of forcing it to look up for collaborators. This is in opposition to service locator solutions and hardcoded wiring: "* takes away the wiring responsibility from the class. entity: a class which purpose is maintain state, with few or no references to external services. 0ser and Post are common examples of entity classes in web applications. >or this entities to be testable without using a database, they should be Plain 3ld Php 3b8ects that do not extend anything. fluent interface: method chaining that provides the ability to write code like 3b8ect."o omething#&."o omething1lse#&."o'notherThing#&; every method returns the ob8ect itself #Qthis&.
6*
0lossar
3i6ko 7every: 'gile coach at )oogle, guru of T"" and design for testability. newable: class with little behavior and lot of state, such as tring or an entity. 4ot prone to dependency in8ection since it may be serialiLed and created at any time. phpDocumentor: auto!documentation tool for the php language. The tool generate 'pi documentation containing method signature by parsing standard comment blocks in the php code. .7. nit: php instance of the x0nit family of testing frameworks. PGP0nit is the standard for testing every kind of php code and it is used extensively throughout this book. refactoring: changing a computer program2s internal structure without modifying its external functional behavior. Eefactoring improves the design of e*isting code and it is often compared to cleaning the dishes. reflection: process by which a computer program can observe and modify its own structure and behavior, e.g. obtaining the list of a class2s methods. re+uire8once'): statement used in php to include other source files. *n php there is no one!time compilation and classes can be included on the fly by re.uiring other php scripts, a 8ob that is usually performed by an autoloader. ,pl: tandard Php Fibrary, the main ob8ect!oriented component of php. *t is included by default in every php S installation but lacks many functionalities. , T: system under test. *n unit tests, indicates a class, while in functional and integration tests an ob8ect graph. Test doubles: replacement used in unit tests to isolate the 0T from collaborators. ,tubs and mocks are explained in chapter ? and V and are the mainly used instances of Test doubles. 'nother type is the >ake ob8ect, which is a running implementation of an interface that is particularly useful in testing for its simplicity #e.g. in!memory database instances&. TDD: development practice where first the developer writes a failing automated test case, then produces code to pass that test and finally refactors the new code to acceptable standards. T"" is
6'
explained in chapter $%. weltanschauung: )erman term for world view, ultimate global vision of a person or organiLation. @y extension, it means philosophy of life. xdebug: PGP extension for powerful debugging and testing. This extension is fundamental to gain introspection on executed code and it is used by phpunit / to generate code coverage reports. x nit: the set of testing frameworks that includes N0nit, PGP0nit, 0nit, 4unit... This frameworks share a very similar 'pi #test cases, set0p, tear"own#&, assertP#&, ...&