Fundamentals of unit testing in PHP usingPHPUnitNicolas A. Bérard-Nault5 May 2011
Some of the goals of test automationFor the stakeholders:Improvingexternalqualityby reducingdefectdensity
Improvinginternalqualityby increasingmaintainability
Reduced short and long-termriskFor the programmers:Facilitatingdefectlocalization
Use tests as documentation
Use tests as a specificationThe automatedtesting continuumIntegration testsUnit testsFunctional tests« Black box »$$$SlowFunctionality« White box »$FastCodeProgrammers are definitelyresponsible for these testsQA is (usually) responsible for these tests
PHPUnit and the xUnitfamilyWritten by Sebastian Bergmann
Member of the xUnitfamily
Direct descendant of sUnitwritten by Kent Beck and jUnit, written by Beck and Erich GammaWebsite: http://www.phpunit.deCode coverageisprovidedusing the xDebug extension: http://www.xdebug.org
Your first PHPUnittest (1)Goal: test an implementation of ROT13System under test:functionrot13($text){$len = strlen($text);for ($i = 0; $i < $len; $i++)    {$text{$i} = chr((ord($text{$i}) - ord('a') + 13) % 26 + ord('a'));    }return$text;}Initial naïve approach: Formulateexpectedbehavior
Your first PHPUnittest (2)Class namehas Test suffixBase class for testsclass Rot13Test extendsPHPUnit_Framework_TestCase{public functiontestWord()    {$this->assertEquals('cheryl', rot13('purely'));    }}Test methodhas test prefixPost-condition verification
Your first PHPUnittest (3)nicobn@nicobn-laptop:~$ phpunit rot13.phpPHPUnit 3.5.5 by Sebastian Bergmann..Time: 0 seconds, Memory: 3.50MbOK (1 test, 1 assertion)The test passes but have wereallycovered all of our bases ?
Preconditions, invariants and postconditionsfunctionrot13($text){$len = strlen($text);for ($i = 0; $i < $len; $i++)    {$text{$i} = chr((ord($text{$i}) - ord('a') + 13) % 26 + ord('a'));    }return$text;}Precondition: $text must be a stringPrecondition: $text must belower caseInvariant: non-alpha characters must remainunchangedPostcondition: each alpha character must bemoved 13 positions Wescrewed up ! How many more tests do weneed ?
NEWFLASH: It’s not a question of how awesomelyawesome of a programmer you are, but a question of methodology !Sad panda issad
Test first ! (1)“Applying ROT13 to a piece of text merely requires examining its alphabetic characters and replacing each one by the letter 13 places further along in the alphabet, wrapping back to the beginning if necessary. A becomes N, B becomes O, and so on up to M, which becomes Z, then the sequence continues at the beginning of the alphabet: N becomes A, O becomes B, and so on to Z, which becomes M. Only those letters which occur in the English alphabet are affected; numbers, symbols, whitespace, and all other characters are left unchanged. Because there are 26 letters in the English alphabet and 26 = 2 × 13, the ROT13 function is its own inverse.”(Wikipedia)
Test first ! (2a)functionrot13($text){}class Rot13Test extendsPHPUnit_Framework_TestCase{public function test_A_Lower()    {$this->assertEquals('n', rot13('a'));    }}There was 1 failure:1) Rot13Test::test_A_LowerFailed asserting that two strings are equal.--- Expected+++ Actual@@ @@-n+
Test first ! (2b)functionrot13($text){return‘n’;}class Rot13Test extendsPHPUnit_Framework_TestCase{public function test_A_Lower()    {$this->assertEquals('n', rot13('a'));    }}PHPUnit 3.5.5 by Sebastian Bergmann..Time: 1 second, Memory: 3.50MbOK (1 test, 1 assertion)
Test first ! (2c)functionrot13($text){returnchr(ord($text{0}) + 13);}class Rot13Test extendsPHPUnit_Framework_TestCase{public function test_A_Lower()    {$this->assertEquals('n', rot13('a'));    }}PHPUnit 3.5.5 by Sebastian Bergmann..Time: 1 second, Memory: 3.50MbOK (1 test, 1 assertion)
Test first ! (3a)functionrot13($text){returnchr(ord($text{0}) + 13);}class Rot13Test extendsPHPUnit_Framework_TestCase{public function test_A_Lower()    {$this->assertEquals('n', rot13('a'));    }public function test_N_Lower()    {$this->assertEquals(‘a', rot13(‘n'));    }}FAIL !
Test first ! (3b)functionrot13($text){return chr((ord($text{0}) - ord('a') + 13) % 26 + ord('a'));}class Rot13Test extendsPHPUnit_Framework_TestCase{public function test_A_Lower()    {$this->assertEquals('n', rot13('a'));    }public function test_N_Lower()    {$this->assertEquals(‘a', rot13(‘n'));    }}PASS !
Test first ! (4a)functionrot13($text){   return chr((ord($text{0}) - ord('a') + 13) % 26 + ord('a'));}class Rot13Test extendsPHPUnit_Framework_TestCase{public function test_A_Lower()    {$this->assertEquals('n', rot13('a'));    }public function test_N_Lower()    {$this->assertEquals(‘a', rot13(‘n'));    }public functiontest_Symbol()    {$this->assertEquals('$', rot13('$'));    }}FAIL !
Test first ! (4b)functionrot13($text){if (!ctype_alnum($text{0})) {return$text{0};    }   return chr((ord($text{0}) - ord('a') + 13) % 26 + ord('a'));}class Rot13Test extendsPHPUnit_Framework_TestCase{public function test_A_Lower()    {$this->assertEquals('n', rot13('a'));    }public function test_N_Lower()    {$this->assertEquals(‘a', rot13(‘n'));    }public functiontest_Symbol()    {$this->assertEquals('$', rot13('$'));    }}PASS !
Test first ! (5a)functionrot13($text){if (!ctype_alnum($text{0})) {return$text{0};    }   return chr((ord($text{0}) - ord('a') + 13) % 26 + ord('a'));}class Rot13Test extendsPHPUnit_Framework_TestCase{/* […] */public functiontest_N_Upper()    {$this->assertEquals('A', rot13('N'));    }}FAIL !
Test first ! (5b)functionrot13($text){if (!ctype_alnum($text{0})) {return$text{0};    }if (ctype_upper($text{0})) {$delta = ord('A');    } else {$delta = ord('a');    }   return chr((ord($text{0}) - $delta + 13) % 26 + $delta);}class Rot13Test extendsPHPUnit_Framework_TestCase{/* […] */public functiontest_N_Upper()    {$this->assertEquals('A', rot13('N'));    }}PASS !
Test first ! (6a)functionrot13($text){if (!ctype_alnum($text{0})) {return$text{0};    }if (ctype_upper($text{0})) {$delta = ord('A');    } else {$delta = ord('a');    }   return chr((ord($text{0}) - $delta + 13) % 26 + $delta);}class Rot13Test extendsPHPUnit_Framework_TestCase{/* […] */public functiontest_N_Purely()    {$this->assertEquals(‘$purely$', rot13(‘$cheryl$'));    }}FAIL !
Test first ! (6b)functionrot13($text){$str = '';$len = strlen($text);for ($i = 0; $i < $len; $i++)    {if (!ctype_alnum($text{$i})) {$str .= $text{$i};        } else {            if (ctype_upper($text{$i})) {$delta = ord('A');            } else {$delta = ord('a');            }$str.= chr((ord($text{$i}) - $delta + 13) % 26 + $delta);        }    }return$str;}PASS !
Test-drivendevelopmentREDGREENREFACTOR
PHPUnit assertionsNote: most assertions have a assertNotXXXX() counterpart.
Test statusDo yourself and yourcolleaguesa favor and mark tests as skipped or incompletewhen relevant !

Unit Testing Presentation

  • 1.
    Fundamentals of unittesting in PHP usingPHPUnitNicolas A. Bérard-Nault5 May 2011
  • 2.
    Some of thegoals of test automationFor the stakeholders:Improvingexternalqualityby reducingdefectdensity
  • 3.
  • 4.
    Reduced short andlong-termriskFor the programmers:Facilitatingdefectlocalization
  • 5.
    Use tests asdocumentation
  • 6.
    Use tests asa specificationThe automatedtesting continuumIntegration testsUnit testsFunctional tests« Black box »$$$SlowFunctionality« White box »$FastCodeProgrammers are definitelyresponsible for these testsQA is (usually) responsible for these tests
  • 7.
    PHPUnit and thexUnitfamilyWritten by Sebastian Bergmann
  • 8.
    Member of thexUnitfamily
  • 9.
    Direct descendant ofsUnitwritten by Kent Beck and jUnit, written by Beck and Erich GammaWebsite: http://www.phpunit.deCode coverageisprovidedusing the xDebug extension: http://www.xdebug.org
  • 10.
    Your first PHPUnittest(1)Goal: test an implementation of ROT13System under test:functionrot13($text){$len = strlen($text);for ($i = 0; $i < $len; $i++) {$text{$i} = chr((ord($text{$i}) - ord('a') + 13) % 26 + ord('a')); }return$text;}Initial naïve approach: Formulateexpectedbehavior
  • 11.
    Your first PHPUnittest(2)Class namehas Test suffixBase class for testsclass Rot13Test extendsPHPUnit_Framework_TestCase{public functiontestWord() {$this->assertEquals('cheryl', rot13('purely')); }}Test methodhas test prefixPost-condition verification
  • 12.
    Your first PHPUnittest(3)nicobn@nicobn-laptop:~$ phpunit rot13.phpPHPUnit 3.5.5 by Sebastian Bergmann..Time: 0 seconds, Memory: 3.50MbOK (1 test, 1 assertion)The test passes but have wereallycovered all of our bases ?
  • 13.
    Preconditions, invariants andpostconditionsfunctionrot13($text){$len = strlen($text);for ($i = 0; $i < $len; $i++) {$text{$i} = chr((ord($text{$i}) - ord('a') + 13) % 26 + ord('a')); }return$text;}Precondition: $text must be a stringPrecondition: $text must belower caseInvariant: non-alpha characters must remainunchangedPostcondition: each alpha character must bemoved 13 positions Wescrewed up ! How many more tests do weneed ?
  • 14.
    NEWFLASH: It’s nota question of how awesomelyawesome of a programmer you are, but a question of methodology !Sad panda issad
  • 15.
    Test first !(1)“Applying ROT13 to a piece of text merely requires examining its alphabetic characters and replacing each one by the letter 13 places further along in the alphabet, wrapping back to the beginning if necessary. A becomes N, B becomes O, and so on up to M, which becomes Z, then the sequence continues at the beginning of the alphabet: N becomes A, O becomes B, and so on to Z, which becomes M. Only those letters which occur in the English alphabet are affected; numbers, symbols, whitespace, and all other characters are left unchanged. Because there are 26 letters in the English alphabet and 26 = 2 × 13, the ROT13 function is its own inverse.”(Wikipedia)
  • 16.
    Test first !(2a)functionrot13($text){}class Rot13Test extendsPHPUnit_Framework_TestCase{public function test_A_Lower() {$this->assertEquals('n', rot13('a')); }}There was 1 failure:1) Rot13Test::test_A_LowerFailed asserting that two strings are equal.--- Expected+++ Actual@@ @@-n+
  • 17.
    Test first !(2b)functionrot13($text){return‘n’;}class Rot13Test extendsPHPUnit_Framework_TestCase{public function test_A_Lower() {$this->assertEquals('n', rot13('a')); }}PHPUnit 3.5.5 by Sebastian Bergmann..Time: 1 second, Memory: 3.50MbOK (1 test, 1 assertion)
  • 18.
    Test first !(2c)functionrot13($text){returnchr(ord($text{0}) + 13);}class Rot13Test extendsPHPUnit_Framework_TestCase{public function test_A_Lower() {$this->assertEquals('n', rot13('a')); }}PHPUnit 3.5.5 by Sebastian Bergmann..Time: 1 second, Memory: 3.50MbOK (1 test, 1 assertion)
  • 19.
    Test first !(3a)functionrot13($text){returnchr(ord($text{0}) + 13);}class Rot13Test extendsPHPUnit_Framework_TestCase{public function test_A_Lower() {$this->assertEquals('n', rot13('a')); }public function test_N_Lower() {$this->assertEquals(‘a', rot13(‘n')); }}FAIL !
  • 20.
    Test first !(3b)functionrot13($text){return chr((ord($text{0}) - ord('a') + 13) % 26 + ord('a'));}class Rot13Test extendsPHPUnit_Framework_TestCase{public function test_A_Lower() {$this->assertEquals('n', rot13('a')); }public function test_N_Lower() {$this->assertEquals(‘a', rot13(‘n')); }}PASS !
  • 21.
    Test first !(4a)functionrot13($text){ return chr((ord($text{0}) - ord('a') + 13) % 26 + ord('a'));}class Rot13Test extendsPHPUnit_Framework_TestCase{public function test_A_Lower() {$this->assertEquals('n', rot13('a')); }public function test_N_Lower() {$this->assertEquals(‘a', rot13(‘n')); }public functiontest_Symbol() {$this->assertEquals('$', rot13('$')); }}FAIL !
  • 22.
    Test first !(4b)functionrot13($text){if (!ctype_alnum($text{0})) {return$text{0}; } return chr((ord($text{0}) - ord('a') + 13) % 26 + ord('a'));}class Rot13Test extendsPHPUnit_Framework_TestCase{public function test_A_Lower() {$this->assertEquals('n', rot13('a')); }public function test_N_Lower() {$this->assertEquals(‘a', rot13(‘n')); }public functiontest_Symbol() {$this->assertEquals('$', rot13('$')); }}PASS !
  • 23.
    Test first !(5a)functionrot13($text){if (!ctype_alnum($text{0})) {return$text{0}; } return chr((ord($text{0}) - ord('a') + 13) % 26 + ord('a'));}class Rot13Test extendsPHPUnit_Framework_TestCase{/* […] */public functiontest_N_Upper() {$this->assertEquals('A', rot13('N')); }}FAIL !
  • 24.
    Test first !(5b)functionrot13($text){if (!ctype_alnum($text{0})) {return$text{0}; }if (ctype_upper($text{0})) {$delta = ord('A'); } else {$delta = ord('a'); } return chr((ord($text{0}) - $delta + 13) % 26 + $delta);}class Rot13Test extendsPHPUnit_Framework_TestCase{/* […] */public functiontest_N_Upper() {$this->assertEquals('A', rot13('N')); }}PASS !
  • 25.
    Test first !(6a)functionrot13($text){if (!ctype_alnum($text{0})) {return$text{0}; }if (ctype_upper($text{0})) {$delta = ord('A'); } else {$delta = ord('a'); } return chr((ord($text{0}) - $delta + 13) % 26 + $delta);}class Rot13Test extendsPHPUnit_Framework_TestCase{/* […] */public functiontest_N_Purely() {$this->assertEquals(‘$purely$', rot13(‘$cheryl$')); }}FAIL !
  • 26.
    Test first !(6b)functionrot13($text){$str = '';$len = strlen($text);for ($i = 0; $i < $len; $i++) {if (!ctype_alnum($text{$i})) {$str .= $text{$i}; } else { if (ctype_upper($text{$i})) {$delta = ord('A'); } else {$delta = ord('a'); }$str.= chr((ord($text{$i}) - $delta + 13) % 26 + $delta); } }return$str;}PASS !
  • 27.
  • 28.
    PHPUnit assertionsNote: mostassertions have a assertNotXXXX() counterpart.
  • 29.
    Test statusDo yourselfand yourcolleaguesa favor and mark tests as skipped or incompletewhen relevant !