Unit testing PHP apps with PHPUnit
Upcoming SlideShare
Loading in...5
×

Like this? Share it with your network

Share

Unit testing PHP apps with PHPUnit

  • 7,188 views
Uploaded on

Unit testing, everyone talks about it and wants to do it but never gets around to actually start testing. Complex spaghetti code and time / budget pressures are often the reasons why nobody dives......

Unit testing, everyone talks about it and wants to do it but never gets around to actually start testing. Complex spaghetti code and time / budget pressures are often the reasons why nobody dives in and gets started with testing. But when the application breaks, and people loose money or worse it's often too late.

In this talk I will take you on a journey with real examples that will show you how you can set up your tests, how to test complex situations with legacy spaghetti code, test web services, database interactions and how to gradually build a solid foundation to safeguard the core code base and everything around it.

Don't you want to be confident when you walk out the office?

More in: Technology
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Be the first to comment
No Downloads

Views

Total Views
7,188
On Slideshare
6,068
From Embeds
1,120
Number of Embeds
15

Actions

Shares
Downloads
36
Comments
0
Likes
9

Embeds 1,120

http://www.dragonbe.com 1,034
https://twitter.com 29
http://feedly.com 26
http://dragonbe2.rssing.com 9
http://www.feedspot.com 6
http://feeds.feedburner.com 5
http://digg.com 3
http://plus.url.google.com 1
http://dragonbe.com.netzcheck.com 1
http://rss.hwarf.com 1
http://www.newsblur.com 1
http://inoreader.com 1
http://translate.googleusercontent.com 1
http://www.inoreader.com 1
https://www.google.be 1

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
    No notes for slide

Transcript

  • 1. Day Camp 4 Developers Day Camp 4 Developers & 2 Unit Testing PHP apps with PHPUnit
  • 2. Michelangelo  van  Dam 2
  • 3. Let’s  talk  about  tes6ng 3
  • 4. Excuses  not  to  test • No  6me   • Not  in  budget   • We  don’t  know  how     -­‐ valid  and  honest  answer   -­‐ right,  like  that’s  going  to  happen   • We  add  unit  tests  a@er  finish  project   •… 4
  • 5. No  excuses 5
  • 6. Unit  tes6ng  is  fun  and  easy! • When  you  write  code   • You  already  test  in  your  head   • Write  out  these  test(s)   • And  protect  your  code  base 6
  • 7. How  to  get  started? 7
  • 8. My  example  code • Get  this  example  from  GitHub   -­‐ hQps://github.com/in2it/Utexamples project/ src/ Utexamples/ Calculator.php autoload.php tests/ Utexamples/ CalculatorTest.php 8
  • 9. Simple  example  class • Add  by  one   -­‐ adds  the  current  value  by  one <?php ! namespace Utexamples; ! ! /** !  * Class that allows us to make all sorts of calculations !  */ ! class Calculator ! { !     protected $_value = 0; ! !     /** !      * Adds the current value by one !      * !      * @return int The value from this method !      */ !     public function addByOne() !     { !         $this->_value++; !         return $this->_value; !     } ! } 9
  • 10. Our  unit  test <?php ! namespace Utexamples; ! ! Class CalculatorTest extends PHPUnit_Framework_TestCase ! { !     public function testCalculatorCanAddByOne() !     { !         $calculator = new Calculator(); !         $result = $calculator->addByOne(); !         $this->assertSame(1, $result); !     } ! } 10
  • 11. My  autoloader <?php ! ! /** !  * Simple autoloader that follow the PHP Standards Recommendation #0 (PSR-0) !  * @see https://github.com/php-fig/fig-standards/blob/master/accepted/ PSR-0.md for more informations. !  * !  * Code inspired from the SplClassLoader RFC !  * @see https://wiki.php.net/rfc/splclassloader#example_implementation !  */ ! spl_autoload_register(function($className) { !     $className = ltrim($className, ''); !     $fileName = ''; !     $namespace = ''; !     if ($lastNsPos = strripos($className, '')) { !         $namespace = substr($className, 0, $lastNsPos); !         $className = substr($className, $lastNsPos + 1); !         $fileName = str_replace(! '', DIRECTORY_SEPARATOR, $namespace) . DIRECTORY_SEPARATOR; !     } !     $fileName = __DIR__ . DIRECTORY_SEPARATOR . $fileName . $className . '.php'; !     if (file_exists($fileName)) { !         require $fileName; ! !         return true; !     } ! !     return false; ! }); 11
  • 12. Running  PHPUnit 12
  • 13. Running  PHPUnit 12
  • 14. A  lot  of  parameters! • Easily  possible  to  make  mistakes   • Every  developer  might  use  different  params   • Not  easy  for  (semi-­‐)automated  tes6ng   -­‐ Using  IDE  for  running  unit  tests 13
  • 15. Let’s  op6mise  this <?xml version="1.0" encoding="UTF-8"?> ! <!-- file: <project>/phpunit.xml --> ! <phpunit bootstrap="./src/autoload.php" colors="true"> ! <testsuite name="Unit Test Example code"> <directory>./tests</directory> </testsuite> ! <filter> <whitelist> <directory suffix=".php">./src</directory> <exclude> <directory suffix=".phtml">./src</directory> </exclude> </whitelist> </filter> ! </phpunit> 14
  • 16. Now  run  more  relaxed 15
  • 17. Now  run  more  relaxed 15
  • 18. We’re  no  grads  anymore 16
  • 19. Real  apps,  real  money 17
  • 20. 18
  • 21. How  to  reach  the  top 19
  • 22. Data  Model  Tes6ng 20
  • 23. Simple  Product  Model Product _productId : integer _code : string _title : string _description : string _image : string _price : float _created : DateTime _modified : DateTime __construct($params : null | array) setProductId($productId : integer) : Product getProductId() : integer setCode($code : string) : Product getCode() : string setTitle($title : string) : Product getTitle() : string setDescription($description : string) : Product getDescription() : string setImage($image : string) : Product getImage() : string setPrice($price : float) : Product getPrice() : float setCreated($created : string | DateTime) : Product getCreated() : DateTime setModified($modified : string | DateTime) : Product getModified() : DateTime populate($data : array) toArray() : array __toString() : string 21
  • 24. A  simple  ProductTest <?php ! namespace UtexamplesModel; ! /** !  * Class ProductTest !  * @package UtexamplesModel !  * @group Model !  */ ! class ProductTest extends PHPUnit_Framework_TestCase ! { !     public function testProductCanBePopulated() !     { !         $data = array ( !             'productId' => 1, !             'code' => 'TSTPROD1', !             'title' => 'Test product 1', !             'description' => 'This is a full description of test product 1', !             'image' => 'image.png', !             'price' => 123.95, !             'created' => '2013-11-20 16:00:00', !             'modified' => '2013-11-20 17:00:00', !         ); ! !         $product = new Product($data); !         $this->assertEquals($data, $product->toArray()); !     } ! } 22
  • 25. A  simple  ProductTest <?php ! namespace UtexamplesModel; ! /** !  * Class ProductTest !  * @package UtexamplesModel !  * @group Model !  */ ! class ProductTest extends PHPUnit_Framework_TestCase ! { !     public function testProductCanBePopulated() !     { !         $data = array ( !             'productId' => 1, !             'code' => 'TSTPROD1', !             'title' => 'Test product 1', !             'description' => 'This is a full description of test product 1', !             'image' => 'image.png', !             'price' => 123.95, !             'created' => '2013-11-20 16:00:00', !             'modified' => '2013-11-20 17:00:00', !         ); ! !         $product = new Product($data); !         $product = new Product($data); !         $this->assertEquals($data, $product->toArray());         $this->assertEquals($data, $product->toArray()); !     } ! } 22
  • 26. Running  the  test 23
  • 27. Running  the  test 23
  • 28. data  fixture     public function goodDataProvider() { !         return array ( !             array ( !                 1, !                 'TSTPROD1', !                 'Test Product 1', !                 'This is a full description of test product 1', !                 'image.png', !                 123.95, !                 '2013-11-20 16:00:00', !                 '2013-11-20 17:00:00', !             ), !             array ( !                 2, !                 'TSTPROD2', !                 'Test Product 2', !                 'This is a full description of test product 2', !                 'image.png', !                 4125.99, !                 '2013-11-20 16:00:00', !                 '2013-11-20 17:00:00', !             ), !         ); !     } 24
  • 29. Using  @dataProvider     /** !      * @dataProvider goodDataProvider !      */ !     public function testProductCanBePopulated( !         $productId, $code, $title, $description, $image, $price, $created, $modified !     ) !     { !         $data = array ( !             'productId' => $productId, !             'code' => $code, !             'title' => $title, !             'description' => $description, !             'image' => $image, !             'price' => $price, !             'created' => $created, !             'modified' => $modified, !         ); ! !         $product = new Product($data); !         $this->assertEquals($data, $product->toArray()); !     } 25
  • 30. Using  @dataProvider     /** !     /** !      * @dataProvider goodDataProvider !      * @dataProvider goodDataProvider !      */ !      */     public function testProductCanBePopulated( !         $productId, $code, $title, $description, $image, $price, $created, $modified !     ) !     { !         $data = array ( !             'productId' => $productId, !             'code' => $code, !             'title' => $title, !             'description' => $description, !             'image' => $image, !             'price' => $price, !             'created' => $created, !             'modified' => $modified, !         ); ! !         $product = new Product($data); !         $this->assertEquals($data, $product->toArray()); !     } 25
  • 31. Running  with  @dataProvider 26
  • 32. Running  with  @dataProvider 26
  • 33. To  protect  and  to  serve 27
  • 34. OWASP  top  10  exploits https://www.owasp.org/index.php/Top_10_2013-Top_10 28
  • 35. Filtering  &  Valida6on 29
  • 36. Libs  you  can  use • Zend  Framework  1:  Zend_Filter_Input   • Zend  Framework  2:  ZendInputFilter   • Symfony:  SymfonyComponentValidator   • Aura:  AuraFrameworkInputFilter   • Lithium:  lithiumu6lValidator   • Laravel:  AppValidator 30
  • 37. Modify  our  Product  class <?php ! namespace UtexamplesModel; ! ! class Product extends ModelAbstract ! { ! ...!     /** !      * @var Zend_Filter_Input The filter/validator for this Product !      */ !     protected $_inputFilter; ! !     /** !      * @var bool The validation of data for this Product !      */ !     protected $_valid; ! !     /** !      * Helper class to create filter and validation rules !      * !      * @access protected !      */ !     protected function _createInputFilter() !     { ! ...!     } ! ...! } 31
  • 38. _createInputFilter()     protected function _createInputFilter() !     { !         $filters = array ( !             'productId' => array('Int'), !             'code' => array ('StripTags', 'StringTrim', 'StringToUpper'), !             'title' => array ('StripTags', 'StringTrim'), !             'description' => array ('StripTags', 'StringTrim'), !             'image' => array ('StripTags', 'StringTrim','StringToLower'), !             'price' => array (), !         ); !         $validators = array ( !             'productId' => array ( !                 'Int', !                 array ('GreaterThan', array ('min' => 0, 'inclusive' => true)), !             ), !             'code' => array ( !                 'Alnum', !                 array ('StringLength', array ('min' => 5, 'max' => 50)), !             ), !             'title' => array ('NotEmpty'), !             'description' => array ('NotEmpty'), !             'image' => array ('NotEmpty'), !             'price' => array ( !                 'Float', !                 array ('GreaterThan', array ('min' => 0, 'inclusive' => true)), !             ), !         ); !         $this->_inputFilter = new Zend_Filter_Input($filters, $validators); !     } 32
  • 39. Modify  your  seQers     /** !      * Set the product code for this Product !      * !      * @param string $code !      * @return Product !      */ !     public function setCode($code) !     { !         $this->_inputFilter->setData(array ('code' => $code)); !         if ($this->_inputFilter->isValid('code')) { !             $this->_code = $this->_inputFilter->code; !             $this->setValid(true); !         } else { !             $this->setValid(false); !         } !         return $this; !     } 33
  • 40. Modify  your  seQers     /** !      * Set the product code for this Product !      * !      * @param string $code !      * @return Product !      */ !     public function setCode($code) !         $this->_inputFilter->setData(array ('code' => $code));     { !         if ($this->_inputFilter->isValid('code')) { !         $this->_inputFilter->setData(array ('code' => $code)); !             $this->_code = $this->_inputFilter->code; !         if ($this->_inputFilter->isValid('code')) { !             $this->_code = $this->_inputFilter->code; !             $this->setValid(true); !             $this->setValid(true); !         } else { !         } else { !             $this->setValid(false); !             $this->setValid(false); !         } ! !         }         return $this; !         return $this;     } ! 33
  • 41. Using  dataproviders  again     public function badDataProvider() !     { !         return array ( !             array ( !                 1, '', '', '', '', 0, !                 '0000-00-00 00:00:00', !                 '0000-00-00 00:00:00', !             ), !             array ( !                 1, !                 '!@#$%^@^&*{}[]=-/'', 'Test Product 1', !                 'This is a full description of test product 1', 'image.png', !                 123.95, '2013-11-20 16:00:00', '2013-11-20 17:00:00', !             ), !             array ( !                 1, '' OR 1=1; --', 'Test Product 1', !                 'This is a full description of test product 1', 'image.png', !                 123.95, '2013-11-20 16:00:00', '2013-11-20 17:00:00', !             ), !         ); !     } 34
  • 42. And  now  we  test  for  valid  data     /** !      * @dataProvider badDataProvider !      */ !     public function testProductRejectsBadData( !         $productId, $code, $title, $description, $image, $price, $created, $modified !     ) !     { !         $data = array ( !             'productId' => $productId, !             'code' => $code, !             'title' => $title, !             'description' => $description, !             'image' => $image, !             'price' => $price, !             'created' => $created, !             'modified' => $modified, !         ); ! !         $product = new Product($data); !         $this->assertFalse($product->isValid()); !     } 35
  • 43. And  now  we  test  for  valid  data     /** !      * @dataProvider badDataProvider !      */ !     public function testProductRejectsBadData( !         $productId, $code, $title, $description, $image, $price, $created, $modified !     ) !     { !         $data = array ( !             'productId' => $productId, !             'code' => $code, !             'title' => $title, !             'description' => $description, !             'image' => $image, !             'price' => $price, !             'created' => $created, !             'modified' => $modified, !         ); ! !         $product = new Product($data); !         $product = new Product($data); !         $this->assertFalse($product->isValid()); !         $this->assertFalse($product->isValid());     } 35
  • 44. Running  our  tests 36
  • 45. Running  our  tests 36
  • 46. Databases,  the  wisdom  fountain 37
  • 47. 4  stages  of  database  tes6ng • Setup  table  fixtures   • Run  tests  on  the  database  interac6ons   • Verify  results  of  tests   • Teardown  the  fixtures 38
  • 48. Data  fixture  data  set 39
  • 49. Hello  DBUnit <?php ! namespace UtexamplesModel; ! ! use PHPUnit_Extensions_Database_DataSet_IDataSet; ! use PHPUnit_Extensions_Database_DB_IDatabaseConnection; ! use PHPUnit_Extensions_Database_DataSet_QueryDataSet; ! ! class ProductDbTest extends PHPUnit_Extensions_Database_TestCase ! { !     protected $_pdo; ! !     public function __construct() !     { !         $this->_pdo = new PDO('sqlite::memory:'); !         $this->_pdo->exec(! file_get_contents(dirname(__DIR__) . ‘/../../data/schema.sqlite.sql')! ); !     } ! !     final public function getConnection() !     { !         return $this->createDefaultDBConnection($this->_pdo, 'sqlite'); !     } ! !     protected function getDataSet() !     { !         return $this->createFlatXMLDataSet(dirname(__DIR__) . '/_files/initialDataSet.xml'); !     } ! } 40
  • 50. Hello  DBUnit <?php ! namespace UtexamplesModel; ! ! use PHPUnit_Extensions_Database_DataSet_IDataSet; ! use PHPUnit_Extensions_Database_DB_IDatabaseConnection; ! use PHPUnit_Extensions_Database_DataSet_QueryDataSet; ! ! class ProductDbTest extends PHPUnit_Extensions_Database_TestCase ! { !     protected $_pdo; ! protected $_pdo; ! ! !     public function __construct() !     { ! public function __construct() !         $this->_pdo = new PDO('sqlite::memory:'); ! { !         $this->_pdo->exec(!     $this->_pdo = new PDO('sqlite::memory:'); ! file_get_contents(dirname(__DIR__) . ‘/../../data/schema.sqlite.sql')!     $this->_pdo->exec(! ); !     } ! file_get_contents(dirname(__DIR__) . ‘/../../data/schema.sqlite.sql')! ! ); !     final public function getConnection() ! }     { !         return $this->createDefaultDBConnection($this->_pdo, 'sqlite'); !     } ! !     protected function getDataSet() !     { !         return $this->createFlatXMLDataSet(dirname(__DIR__) . '/_files/initialDataSet.xml'); !     } ! } 40
  • 51. Hello  DBUnit <?php ! namespace UtexamplesModel; ! ! use PHPUnit_Extensions_Database_DataSet_IDataSet; ! use PHPUnit_Extensions_Database_DB_IDatabaseConnection; ! use PHPUnit_Extensions_Database_DataSet_QueryDataSet; ! ! class ProductDbTest extends PHPUnit_Extensions_Database_TestCase ! { !     protected $_pdo; ! !     public function __construct() !     { !         $this->_pdo = new PDO('sqlite::memory:'); !         $this->_pdo->exec(! file_get_contents(dirname(__DIR__) . ‘/../../data/schema.sqlite.sql')! ); ! final public function getConnection() !     } ! { ! !     return $this->createDefaultDBConnection($this->_pdo, 'sqlite'); !     final public function getConnection() !     { ! }         return $this->createDefaultDBConnection($this->_pdo, 'sqlite'); !     } ! !     protected function getDataSet() !     { !         return $this->createFlatXMLDataSet(dirname(__DIR__) . '/_files/initialDataSet.xml'); !     } ! } 40
  • 52. Hello  DBUnit <?php ! namespace UtexamplesModel; ! ! use PHPUnit_Extensions_Database_DataSet_IDataSet; ! use PHPUnit_Extensions_Database_DB_IDatabaseConnection; ! use PHPUnit_Extensions_Database_DataSet_QueryDataSet; ! ! class ProductDbTest extends PHPUnit_Extensions_Database_TestCase ! { !     protected $_pdo; ! !     public function __construct() !     { !         $this->_pdo = new PDO('sqlite::memory:'); !         $this->_pdo->exec(! file_get_contents(dirname(__DIR__) . ‘/../../data/schema.sqlite.sql')! ); !     } ! !     final public function getConnection() !     { ! protected function getDataSet() !         return $this->createDefaultDBConnection($this->_pdo, 'sqlite'); ! { !     } !     return $this->createFlatXMLDataSet(! ! dirname(__DIR__) . ‘/_files/initialDataSet.xml'!     protected function getDataSet() !     {); ! !         return $this->createFlatXMLDataSet(dirname(__DIR__) . '/_files/initialDataSet.xml'); ! }     } ! } 40
  • 53. Hello  DBUnit <?php ! namespace UtexamplesModel; ! ! use PHPUnit_Extensions_Database_DataSet_IDataSet; ! use PHPUnit_Extensions_Database_DB_IDatabaseConnection; ! use PHPUnit_Extensions_Database_DataSet_QueryDataSet; ! ! class ProductDbTest extends PHPUnit_Extensions_Database_TestCase ! { !     protected $_pdo; ! !     public function __construct() !     { !         $this->_pdo = new PDO('sqlite::memory:'); !         $this->_pdo->exec(! file_get_contents(dirname(__DIR__) . ‘/../../data/schema.sqlite.sql')! ); !     } ! !     final public function getConnection() !     { ! protected function getDataSet() !         return $this->createDefaultDBConnection($this->_pdo, 'sqlite'); ! { !     } !     return $this->createFlatXMLDataSet(! ! dirname(__DIR__) . ‘/_files/initialDataSet.xml'!     protected function getDataSet() !     {); ! !         return $this->createFlatXMLDataSet(dirname(__DIR__) . '/_files/initialDataSet.xml'); ! }     } ! } 40
  • 54. Ini6alDataset <?xml version="1.0" encoding="UTF-8"?> <dataset> <product productId="1" code="TEST CODE1" title="First test product" description="This is our first test product" image="http://www.example.com/image/image.png" price="150.95" created="2013-03-30 10:11:12" modified="2013-12-11 09:08:07"/> <product productId="2" code="TEST CODE2" title="Second test product" description="This is our second test product" image="http://www.example.com/image/image.png" price="19999.00" created="2013-03-30 10:11:12" modified="2013-12-11 09:08:07"/> <product productId="3" code="TEST CODE3" title="Third test product" description="This is our third test product" image="http://www.example.com/image/image.png" price="0.45" created="2013-03-30 10:11:12" modified="2013-12-11 09:08:07"/> </dataset> 41
  • 55. First  DB  Test     public function testProductsCanBeLoadedFromDatabase() !     { !         $currentDataset = $this->getDataSet(); ! !         $expectedDataset = $this->createFlatXmlDataSet( !             dirname(__DIR__) . '/_files/selectDataSet.xml' !         ); ! !         $this->assertDataSetsEqual($expectedDataset, $currentDataset); !     } 42
  • 56. Adding  Data  Test     public function testProductAddToDatabase() !     { !         $data = array ( !             'code' => 'TST', !             'title' => 'Test', !             'description' => 'Testing Test', !             'image' => 'http://www.example.com/image.png', !             'price' => 10.00, !             'created' => '2013-12-15 01:55:00', !             'modified' => '2013-12-20 16:00:00', !         ); ! !         $product = new Product($data); !         $product->setPdo($this->_pdo); !         $product->save(); ! !         $expectedDs = $this->createFlatXMLDataSet( !             dirname(__DIR__) . '/_files/addProductDataSet.xml' !         ); !         $currentDs = $this->getConnection()->createDataSet(array ('product')); !         $this->assertDataSetsEqual($expectedDs, $currentDs); !     } 43
  • 57. addProductDataSet.xml <?xml version="1.0" encoding="UTF-8"?> <dataset> ... <product productId="4" code="TEST" title="Test" description="Testing Test" image="http://www.example.com/image.png" price="10.0" created="2013-12-15 01:55:00" modified="2013-12-20 16:00:00”/> </dataset> 44
  • 58. Running  our  DBUnit  test 45
  • 59. Running  our  DBUnit  test 45
  • 60. Oops 46
  • 61. Oops 46
  • 62. Oops 46
  • 63. Oh  no,  I  made  a  TYPO! 47
  • 64. addProductDataSet.xml <?xml version="1.0" encoding="UTF-8"?> <dataset> ... <product productId="4" code="TST" title="Test" description="Testing Test" image="http://www.example.com/image.png" price="10.0" created="2013-12-15 01:55:00" $data = array ( ! modified="2013-12-20 16:00:00”/>     'code' => 'TST', ! </dataset>     'title' => 'Test', !     'description' => 'Testing Test', !     'image' => 'http://www.example.com/image.png', !     'price' => 10.00, !     'created' => '2013-12-15 01:55:00', !     'modified' => '2013-12-20 16:00:00', ! ); 48
  • 65. Running  our  DBUnit  test 49
  • 66. Running  our  DBUnit  test 49
  • 67. Everybody  happy 50
  • 68. Some  downsides • Tes6ng  databases  takes  6me   -­‐ -­‐ -­‐ -­‐ -­‐ create  a  real  database  connec6on   reini6alise  the  database  (load  schema,  truncate  tables)   load  ini6al  state  before  test  (with  each  test)   execute  on  the  database   compare  expected  result  with  actual  result 51
  • 69. We  can  do  beQer! 52
  • 70. Mock  objects • They  replace  an  object  for  tes6ng   -­‐ -­‐ a  class  with  all  methods   a  class  with  a  single  method   -­‐ -­‐ -­‐ databases   web  services   file  systems   -­‐ once  you  got  everything  set  up • Since  they  replace  “expansive”  connec6ons   • Are  quicker  and  more  reliable  to  test   53
  • 71. Same  tests,  but  now  mocked <?php ! ! namespace UtexamplesModel; ! ! use PDO; ! use PDOStatement; ! ! class ProductMockTest extends PHPUnit_Framework_TestCase ! { !     public function testProductsCanBeLoadedFromDatabase() !     { ! !     } ! !     public function testProductAddToDatabase() !     { ! !     } ! } 54
  • 72. testProductsCanBeLoadedFromDatabase     public function testProductsCanBeLoadedFromDatabase() !     { !         $data = array (); !         // let's mock the prepare statement !         $pdostmt = $this->getMock('PDOStatement', array ('execute', 'fetchAll')); !         $pdostmt->expects($this->atLeastOnce()) !             ->method('execute') !             ->will($this->returnValue(true)); !         $pdostmt->expects($this->atLeastOnce()) !             ->method('fetchAll') !             ->will($this->returnValue($data)); !         // let's mock the PDO object and return the mocked statement !         $pdo = $this->getMock('PDO', array ('prepare'), array ('sqlite::memory')); !         $pdo->expects($this->atLeastOnce()) !             ->method('prepare') !             ->will($this->returnValue($pdostmt)); ! !         $productCollection = new ProductCollection(); !         $productCollection->setPdo($pdo); !         $productCollection->fetchAll(); ! !         $this->assertEquals($data, $productCollection->toArray()); !     } 55
  • 73. My  $data  array         $data = array ( !             array ( !                 'productId' => 1, !                 'code' => 'TST1', !                 'title' => 'Test 1', !                 'description' => 'Testing product 1', !                 'image' => 'http://www.example.com/image1.png', !                 'price' => 10.00, !                 'created' => '2013-12-01 01:55:00', !                 'modified' => '2013-12-20 16:00:00', !             ), !             array ( !                 'productId' => 2, !                 'code' => 'TST2', !                 'title' => 'Test 2', !                 'description' => 'Testing product 2', !                 'image' => 'http://www.example.com/image2.png', !                 'price' => 199.95, !                 'created' => '2013-12-02 02:55:00', !                 'modified' => '2013-12-20 16:00:00', !             ), !         ); 56
  • 74. testProductAddToDatabase     public function testProductAddToDatabase() !     { !         $pdostmt = $this->getMock('PDOStatement', array ('execute')); !         $pdostmt->expects($this->atLeastOnce()) !             ->method('execute') !             ->will($this->returnValue(true));!         $pdo = $this->getMock('PDO', array ('prepare'), array ('sqlite::memory')); !         $pdo->expects($this->once()) !             ->method('prepare') !             ->will($this->returnValue($pdostmt)); ! !         $data = array ( !             'code' => 'TST', !             'title' => 'Test', !             'description' => 'Testing Test', !             'image' => 'http://www.example.com/image.png', !             'price' => 10.00, !             'created' => '2013-12-15 01:55:00', !             'modified' => '2013-12-20 16:00:00', !         ); !         $product = new Product($data); !         $product->setPdo($pdo); !         $product->save(); ! !         // The model has mentioning of productId !         $data['productId'] = null; !         $this->assertEquals($data, $product->toArray()); !     } 57
  • 75. Running  Data  Mocking 58
  • 76. Running  Data  Mocking 58
  • 77. Why  the  extra  work? • Your  databases  are  fully  tested,  no  need  to  do   it  yourself  again   • The  connec6ons  are  expensive  and  delay  your   tests   • Your  tes6ng  code  that  needs  to  handle  the  data   it  gets,  no  maQer  where  it  gets  it 59
  • 78. Web  Services 60
  • 79. Example:  joind.in 61
  • 80. Joindin  Case:  talks.feryn.eu Fork  it:  hQps://github.com/ThijsFeryn/talks.feryn.eu 62
  • 81. API  Docs  are  your  friend 63
  • 82. Joindin  Test <?php ! class Zftest_Service_JoindinTest extends PHPUnit_Framework_TestCase ! { !     protected $_joindin; !     protected $_settings; !      !     protected function setUp() !     { !         $this->_joindin = new Zftest_Service_Joindin(); !         $settings = simplexml_load_file(realpath( !             APPLICATION_PATH . '/../tests/_files/settings.xml')); !         $this->_settings = $settings->joindin; !         parent::setUp(); !     } !     protected function tearDown() !     { !         parent::tearDown(); !         $this->_joindin = null; !     } ! } 64
  • 83. Joindin  Test  (2) public function testJoindinCanGetUserDetails() ! { !     $expected = '<?xml version="1.0"?><response><item><username>DragonBe</ username><full_name>Michelangelo van Dam</full_name><ID>19</ID><last_login>1303248639</ last_login></item></response>'; !     $this->_joindin->setUsername($this->_settings->username) !                    ->setPassword($this->_settings->password); !     $actual = $this->_joindin->user()->getDetail(); !     $this->assertXmlStringEqualsXmlString($expected, $actual); ! } ! ! public function testJoindinCanCheckStatus() ! { !     $date = new DateTime(); !     $date->setTimezone(new DateTimeZone('UTC')); !     $expected = '<?xml version="1.0"?><response><dt>' . $date->format('r') . '</ dt><test_string>testing unit test</test_string></response>'; !     $actual = $this->_joindin->site()->getStatus('testing unit test'); !     $this->assertXmlStringEqualsXmlString($expected, $actual); ! } 65
  • 84. Running  the  test 66
  • 85. Running  the  test 66
  • 86. Euh…  what  just  happened? 1) Zftest_Service_JoindinTest::testJoindinCanGetUserDetails Failed asserting that two strings are equal. --- Expected +++ Actual @@ @@ <ID>19</ID> <last_login>1303248639</last_login> + <last_login>1303250271</last_login> </item> </response> 67
  • 87. And  this? 2) Zftest_Service_JoindinTest::testJoindinCanCheckStatus Failed asserting that two strings are equal. --- Expected +++ Actual @@ @@ <?xml version="1.0"?> <response> - <dt>Tue, 19 Apr 2011 22:26:40 +0000</dt> + <dt>Tue, 19 Apr 2011 22:26:41 +0000</dt> <test_string>testing unit test</test_string> </response> 68
  • 88. No  dispair,  we  help  you  out! 69
  • 89. Let’s  mock  the  HTTP  client <?php ! class Zftest_Service_JoindinTest extends PHPUnit_Framework_TestCase ! { !     protected $_joindin; !     protected $_settings; !      !     protected function setUp() !     { !         $this->_joindin = new Zftest_Service_Joindin(); !         $client = new Zend_Http_Client(); !         $client->setAdapter(new Zend_Http_Client_Adapter_Test()); !         $this->_joindin->setClient($client); !         $settings = simplexml_load_file(realpath( !             APPLICATION_PATH . '/../tests/_files/settings.xml')); !         $this->_settings = $settings->joindin; !         parent::setUp(); !     } !     protected function tearDown() !     { !         parent::tearDown(); !         $this->_joindin = null; !     } ! } 70
  • 90. Let’s  mock  the  HTTP  client <?php ! class Zftest_Service_JoindinTest extends PHPUnit_Framework_TestCase ! { !     protected $_joindin; !     protected $_settings; !      !     protected function setUp() !     { !         $this->_joindin = new Zftest_Service_Joindin(); !         $client = new Zend_Http_Client(); ! $client->setAdapter(new Zend_Http_Client_Adapter_Test());         $client->setAdapter(new Zend_Http_Client_Adapter_Test()); ! $this->_joindin->setClient($client); !         $this->_joindin->setClient($client); !         $settings = simplexml_load_file(realpath( ! $settings = simplexml_load_file(realpath( !             APPLICATION_PATH . '/../tests/_files/settings.xml')); ! APPLICATION_PATH . '/../tests/_files/settings.xml'));         $this->_settings = $settings->joindin; !         parent::setUp(); !     } !     protected function tearDown() !     { !         parent::tearDown(); !         $this->_joindin = null; !     } ! } ! 70
  • 91. Mocking  the  response public function testJoindinCanGetUserDetails() ! { !     $response = <<<EOS ! HTTP/1.1 200 OK ! Content-type: text/xml ! ! <?xml version="1.0"?> ! <response> !   <item> !     <username>DragonBe</username> !     <full_name>Michelangelo van Dam</full_name> !     <ID>19</ID> !     <last_login>1303248639</last_login> !   </item> ! </response>         ! EOS; !     $client = $this->_joindin->getClient()->getAdapter()->setResponse($response); !     $expected = '<?xml version="1.0"?><response><item><username>DragonBe</ username><full_name>Michelangelo van Dam</full_name><ID>19</ID><last_login>1303248639</ last_login></item></response>'; !     $this->_joindin->setUsername($this->_settings->username) !                    ->setPassword($this->_settings->password); !     $actual = $this->_joindin->user()->getDetail(); !     $this->assertXmlStringEqualsXmlString($expected, $actual); ! } 71
  • 92. Mocking  the  response public function testJoindinCanGetUserDetails() ! { !     $response = <<<EOS ! HTTP/1.1 200 OK ! Content-type: text/xml ! ! <?xml version="1.0"?> ! <response> !   <item> !     <username>DragonBe</username> !     <full_name>Michelangelo van Dam</full_name> !     <ID>19</ID> !     <last_login>1303248639</last_login> !   </item> ! </response>         ! EOS; ! $client = $this->_joindin->getClient()->getAdapter()->setResponse($response);     $client = $this->_joindin->getClient()->getAdapter()->setResponse($response); !     $expected = '<?xml version="1.0"?><response><item><username>DragonBe</ username><full_name>Michelangelo van Dam</full_name><ID>19</ID><last_login>1303248639</ last_login></item></response>'; !     $this->_joindin->setUsername($this->_settings->username) !                    ->setPassword($this->_settings->password); !     $actual = $this->_joindin->user()->getDetail(); !     $this->assertXmlStringEqualsXmlString($expected, $actual); ! } 71
  • 93. Same  here public function testJoindinCanCheckStatus() ! { !     $date = new DateTime(); !     $date->setTimezone(new DateTimeZone('UTC')); !     $response = <<<EOS ! HTTP/1.1 200 OK ! Content-type: text/xml ! ! <?xml version="1.0"?> ! <response> !   <dt>{$date->format('r')}</dt> !   <test_string>testing unit test</test_string> ! </response>         ! EOS; ! !     $client = $this->_joindin->getClient() !                              ->getAdapter()->setResponse($response);! !     $expected = '<?xml version="1.0"?><response><dt>' . $date->format('r') . '</ dt><test_string>testing unit test</test_string></response>'; !     $actual = $this->_joindin->site()->getStatus('testing unit test'); !     $this->assertXmlStringEqualsXmlString($expected, $actual); ! } 72
  • 94. Same  here public function testJoindinCanCheckStatus() ! { !     $date = new DateTime(); !     $date->setTimezone(new DateTimeZone('UTC')); !     $response = <<<EOS ! HTTP/1.1 200 OK ! Content-type: text/xml ! ! <?xml version="1.0"?> ! <response> !   <dt>{$date->format('r')}</dt> !   <test_string>testing unit test</test_string> ! </response>         ! EOS; ! ! $client = $this->_joindin->getClient() !     $client = $this->_joindin->getClient() !                          ->getAdapter()->setResponse($response);                              ->getAdapter()->setResponse($response);! !     $expected = '<?xml version="1.0"?><response><dt>' . $date->format('r') . '</ dt><test_string>testing unit test</test_string></response>'; !     $actual = $this->_joindin->site()->getStatus('testing unit test'); !     $this->assertXmlStringEqualsXmlString($expected, $actual); ! } 72
  • 95. Now  we’re  good 73
  • 96. Now  we’re  good 73
  • 97. Conclusion 74
  • 98. Tes6ng  is  easy 75
  • 99. Even  for  spaghen  code 76
  • 100. Posi6ve  &  Nega6ve  tests 77
  • 101. Doing  it  more,  makes  you  beQer 78
  • 102. Recommended  reading Click on the images to view www.owasp.org planet.phpunit.de 79
  • 103. #PHPBNL14 January 25 - 26, 2014 phpcon.eu 80
  • 104. https://joind.in/10113 If you liked it, thank you! If not, tell me how to improve this talk 81
  • 105. Michelangelo van Dam Zend Certified Engineer ! michelangelo@in2it.be PHP Consulting - QA audits - Training ! www.in2it.be 82
  • 106. Credits • Me:  hQp://www.flickr.com/photos/akrabat/8784318813   • CrashTest:  hQp://www.flickr.com/photos/digi6zedchaos/3964206549   • Chris:  hQp://www.flickr.com/photos/akrabat/8421560178   • Nike:  hQp://www.flickr.com/photos/japokskee/4393860599   • Grads:  hQp://www.flickr.com/photos/ajschwegler/525829339   • Econopoly:  hQp://www.flickr.com/photos/danielbroche/2258988806   • Disaster:  hQp://www.flickr.com/photos/eschipul/1484495808/   • Mountain:  hQp://www.flickr.com/photos/jfdervin/2510535266   • Data  Store:  hQp://www.flickr.com/photos/comedynose/7048321621   • Protect:  hQp://www.flickr.com/photos/boltowlue/5724934828   • Owl:  hQp://www.flickr.com/photos/15016964@N02/9425608812   • Register:  hQp://www.flickr.com/photos/taedc/5466788868   • Crying  Baby:  hQp://www.flickr.com/photos/bibbit/5456802728   • Smiling  Donkey:  hQp://www.flickr.com/photos/smkybear/2239030703   • Jump  high:  hQp://www.flickr.com/photos/96748294@N06/9356699040   • Chipmunk:  hQp://www.flickr.com/photos/exfordy/1184487050   • Easy:  hQp://www.flickr.com/photos/dalismustaches/223972376   • Spaghen:  hQp://www.flickr.com/photos/lablasco/5512066970   • BaQery:  hQp://www.flickr.com/photos/shalf/6088539194   • Elephpant:  hQp://www.flickr.com/photos/dragonbe/11403208686 83
  • 107. Thank  you 84