Unit testing PHP apps with PHPUnit

107
Day Camp 4 Developers 2 & Unit Testing PHP apps with PHPUnit

Transcript of Unit testing PHP apps with PHPUnit

Page 1: Unit testing PHP apps with PHPUnit

Day Camp4 Developers

Day Camp4 Developers

Day Camp4 Developers

Day Camp4 Developers

DC4D

2&

Unit Testing PHP apps with PHPUnit

Page 2: Unit testing PHP apps with PHPUnit

Michelangelo  van  Dam

���2

Page 3: Unit testing PHP apps with PHPUnit

Let’s  talk  about  tes6ng

���3

Page 4: Unit testing PHP apps with PHPUnit

Excuses  not  to  test

• No  6me  • Not  in  budget  • We  don’t  know  how    -­‐ valid  and  honest  answer  

• We  add  unit  tests  a@er  finish  project  -­‐ right,  like  that’s  going  to  happen  

• …

���4

Page 5: Unit testing PHP apps with PHPUnit

No  excuses

���5

Page 6: Unit testing PHP apps with PHPUnit

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

Page 7: Unit testing PHP apps with PHPUnit

How  to  get  started?

���7

Page 8: Unit testing PHP apps with PHPUnit

project/ src/ Utexamples/ Calculator.php autoload.php tests/ Utexamples/ CalculatorTest.php

My  example  code

• Get  this  example  from  GitHub  -­‐ hQps://github.com/in2it/Utexamples

���8

Page 9: Unit testing PHP apps with PHPUnit

<?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; !    } !}

Simple  example  class• Add  by  one  -­‐ adds  the  current  value  by  one

���9

Page 10: Unit testing PHP apps with PHPUnit

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

Page 11: Unit testing PHP apps with PHPUnit

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

Page 12: Unit testing PHP apps with PHPUnit

Running  PHPUnit

���12

Page 13: Unit testing PHP apps with PHPUnit

Running  PHPUnit

���12

Page 14: Unit testing PHP apps with PHPUnit

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

Page 15: Unit testing PHP apps with PHPUnit

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

Page 16: Unit testing PHP apps with PHPUnit

Now  run  more  relaxed

���15

Page 17: Unit testing PHP apps with PHPUnit

Now  run  more  relaxed

���15

Page 18: Unit testing PHP apps with PHPUnit

We’re  no  grads  anymore

���16

Page 19: Unit testing PHP apps with PHPUnit

Real  apps,  real  money

���17

Page 20: Unit testing PHP apps with PHPUnit

���18

Page 21: Unit testing PHP apps with PHPUnit

How  to  reach  the  top

���19

Page 22: Unit testing PHP apps with PHPUnit

Data  Model  Tes6ng

���20

Page 23: Unit testing PHP apps with PHPUnit

Simple  Product  Model

���21

_productId : integer_code : string_title : string_description : string_image : string_price : float_created : DateTime_modified : DateTime__construct($params : null | array)

setProductId($productId : integer) : ProductgetProductId() : integersetCode($code : string) : ProductgetCode() : stringsetTitle($title : string) : ProductgetTitle() : stringsetDescription($description : string) : ProductgetDescription() : stringsetImage($image : string) : ProductgetImage() : stringsetPrice($price : float) : ProductgetPrice() : floatsetCreated($created : string | DateTime) : ProductgetCreated() : DateTimesetModified($modified : string | DateTime) : ProductgetModified() : DateTime

populate($data : array)toArray() : array__toString() : string

Product

Page 24: Unit testing PHP apps with PHPUnit

A  simple  ProductTest<?php !namespace Utexamples\Model; !/** ! * Class ProductTest ! * @package Utexamples\Model ! * @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

Page 25: Unit testing PHP apps with PHPUnit

A  simple  ProductTest<?php !namespace Utexamples\Model; !/** ! * Class ProductTest ! * @package Utexamples\Model ! * @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

        $product = new Product($data); !        $this->assertEquals($data, $product->toArray());

Page 26: Unit testing PHP apps with PHPUnit

Running  the  test

���23

Page 27: Unit testing PHP apps with PHPUnit

Running  the  test

���23

Page 28: Unit testing PHP apps with PHPUnit

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

Page 29: Unit testing PHP apps with PHPUnit

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

Page 30: Unit testing PHP apps with PHPUnit

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

    /** !     * @dataProvider goodDataProvider !     */

Page 31: Unit testing PHP apps with PHPUnit

Running  with  @dataProvider

���26

Page 32: Unit testing PHP apps with PHPUnit

Running  with  @dataProvider

���26

Page 33: Unit testing PHP apps with PHPUnit

To  protect  and  to  serve

���27

Page 34: Unit testing PHP apps with PHPUnit

OWASP  top  10  exploits

���28

https://www.owasp.org/index.php/Top_10_2013-Top_10

Page 35: Unit testing PHP apps with PHPUnit

Filtering  &  Valida6on

���29

Page 36: Unit testing PHP apps with PHPUnit

Libs  you  can  use

• Zend  Framework  1:  Zend_Filter_Input  • Zend  Framework  2:  Zend\InputFilter  • Symfony:  Symfony\Component\Validator  • Aura:  Aura\Framework\Input\Filter  • Lithium:  lithium\u6l\Validator  • Laravel:  App\Validator

���30

Page 37: Unit testing PHP apps with PHPUnit

Modify  our  Product  class<?php !namespace Utexamples\Model; !!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

Page 38: Unit testing PHP apps with PHPUnit

_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

Page 39: Unit testing PHP apps with PHPUnit

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

Page 40: Unit testing PHP apps with PHPUnit

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

        $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;

Page 41: Unit testing PHP apps with PHPUnit

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

Page 42: Unit testing PHP apps with PHPUnit

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

Page 43: Unit testing PHP apps with PHPUnit

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

        $product = new Product($data); !        $this->assertFalse($product->isValid());

Page 44: Unit testing PHP apps with PHPUnit

Running  our  tests

���36

Page 45: Unit testing PHP apps with PHPUnit

Running  our  tests

���36

Page 46: Unit testing PHP apps with PHPUnit

Databases,  the  wisdom  fountain

���37

Page 47: Unit testing PHP apps with PHPUnit

4  stages  of  database  tes6ng

• Setup  table  fixtures  • Run  tests  on  the  database  interac6ons  • Verify  results  of  tests  • Teardown  the  fixtures

���38

Page 48: Unit testing PHP apps with PHPUnit

Data  fixture  data  set

���39

Page 49: Unit testing PHP apps with PHPUnit

Hello  DBUnit<?php !namespace Utexamples\Model; !!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

Page 50: Unit testing PHP apps with PHPUnit

Hello  DBUnit<?php !namespace Utexamples\Model; !!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

protected $_pdo; !!public function __construct() !{ !    $this->_pdo = new \PDO('sqlite::memory:'); !    $this->_pdo->exec(! file_get_contents(dirname(__DIR__) . ‘/../../data/schema.sqlite.sql')! ); !}

Page 51: Unit testing PHP apps with PHPUnit

Hello  DBUnit<?php !namespace Utexamples\Model; !!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

final public function getConnection() !{ !    return $this->createDefaultDBConnection($this->_pdo, 'sqlite'); !}

Page 52: Unit testing PHP apps with PHPUnit

Hello  DBUnit<?php !namespace Utexamples\Model; !!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

protected function getDataSet() !{ !    return $this->createFlatXMLDataSet(! dirname(__DIR__) . ‘/_files/initialDataSet.xml'! ); !}

Page 53: Unit testing PHP apps with PHPUnit

Hello  DBUnit<?php !namespace Utexamples\Model; !!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

protected function getDataSet() !{ !    return $this->createFlatXMLDataSet(! dirname(__DIR__) . ‘/_files/initialDataSet.xml'! ); !}

Page 54: Unit testing PHP apps with PHPUnit

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

Page 55: Unit testing PHP apps with PHPUnit

First  DB  Test    public function testProductsCanBeLoadedFromDatabase() !    { !        $currentDataset = $this->getDataSet(); !!        $expectedDataset = $this->createFlatXmlDataSet( !            dirname(__DIR__) . '/_files/selectDataSet.xml' !        ); !!        $this->assertDataSetsEqual($expectedDataset, $currentDataset); !    }

���42

Page 56: Unit testing PHP apps with PHPUnit

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

Page 57: Unit testing PHP apps with PHPUnit

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

Page 58: Unit testing PHP apps with PHPUnit

Running  our  DBUnit  test

���45

Page 59: Unit testing PHP apps with PHPUnit

Running  our  DBUnit  test

���45

Page 60: Unit testing PHP apps with PHPUnit

Oops

���46

Page 61: Unit testing PHP apps with PHPUnit

Oops

���46

Page 62: Unit testing PHP apps with PHPUnit

Oops

���46

Page 63: Unit testing PHP apps with PHPUnit

Oh  no,  I  made  a  TYPO!

���47

Page 64: Unit testing PHP apps with PHPUnit

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" modified="2013-12-20 16:00:00”/> </dataset>

���48

$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', !);

Page 65: Unit testing PHP apps with PHPUnit

Running  our  DBUnit  test

���49

Page 66: Unit testing PHP apps with PHPUnit

Running  our  DBUnit  test

���49

Page 67: Unit testing PHP apps with PHPUnit

Everybody  happy

���50

Page 68: Unit testing PHP apps with PHPUnit

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

Page 69: Unit testing PHP apps with PHPUnit

We  can  do  beQer!

���52

Page 70: Unit testing PHP apps with PHPUnit

Mock  objects

• They  replace  an  object  for  tes6ng  -­‐ a  class  with  all  methods  

-­‐ a  class  with  a  single  method  

• Since  they  replace  “expansive”  connec6ons  -­‐ databases  

-­‐ web  services  

-­‐ file  systems  

• Are  quicker  and  more  reliable  to  test  -­‐ once  you  got  everything  set  up

���53

Page 71: Unit testing PHP apps with PHPUnit

Same  tests,  but  now  mocked<?php !!namespace Utexamples\Model; !!use \PDO; !use \PDOStatement; !!class ProductMockTest extends \PHPUnit_Framework_TestCase !{ !    public function testProductsCanBeLoadedFromDatabase() !    { !!    } !!    public function testProductAddToDatabase() !    { !!    } !}

���54

Page 72: Unit testing PHP apps with PHPUnit

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

Page 73: Unit testing PHP apps with PHPUnit

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

Page 74: Unit testing PHP apps with PHPUnit

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

Page 75: Unit testing PHP apps with PHPUnit

Running  Data  Mocking

���58

Page 76: Unit testing PHP apps with PHPUnit

Running  Data  Mocking

���58

Page 77: Unit testing PHP apps with PHPUnit

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

Page 78: Unit testing PHP apps with PHPUnit

Web  Services

���60

Page 79: Unit testing PHP apps with PHPUnit

Example:  joind.in

���61

Page 80: Unit testing PHP apps with PHPUnit

Joindin  Case:  talks.feryn.eu

���62

Fork  it:  hQps://github.com/ThijsFeryn/talks.feryn.eu

Page 81: Unit testing PHP apps with PHPUnit

API  Docs  are  your  friend

���63

Page 82: Unit testing PHP apps with PHPUnit

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

Page 83: Unit testing PHP apps with PHPUnit

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

Page 84: Unit testing PHP apps with PHPUnit

Running  the  test

���66

Page 85: Unit testing PHP apps with PHPUnit

Running  the  test

���66

Page 86: Unit testing PHP apps with PHPUnit

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

Page 87: Unit testing PHP apps with PHPUnit

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

Page 88: Unit testing PHP apps with PHPUnit

No  dispair,  we  help  you  out!

���69

Page 89: Unit testing PHP apps with PHPUnit

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

Page 90: Unit testing PHP apps with PHPUnit

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

$client->setAdapter(new Zend_Http_Client_Adapter_Test()); !$this->_joindin->setClient($client); !$settings = simplexml_load_file(realpath( ! APPLICATION_PATH . '/../tests/_files/settings.xml'));

Page 91: Unit testing PHP apps with PHPUnit

Mocking  the  responsepublic 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

Page 92: Unit testing PHP apps with PHPUnit

Mocking  the  responsepublic 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

$client = $this->_joindin->getClient()->getAdapter()->setResponse($response);

Page 93: Unit testing PHP apps with PHPUnit

Same  herepublic 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

Page 94: Unit testing PHP apps with PHPUnit

Same  herepublic 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

$client = $this->_joindin->getClient() !                         ->getAdapter()->setResponse($response);

Page 95: Unit testing PHP apps with PHPUnit

Now  we’re  good

���73

Page 96: Unit testing PHP apps with PHPUnit

Now  we’re  good

���73

Page 97: Unit testing PHP apps with PHPUnit

Conclusion

���74

Page 98: Unit testing PHP apps with PHPUnit

Tes6ng  is  easy

���75

Page 99: Unit testing PHP apps with PHPUnit

Even  for  spaghen  code

���76

Page 100: Unit testing PHP apps with PHPUnit

Posi6ve  &  Nega6ve  tests

���77

Page 101: Unit testing PHP apps with PHPUnit

Doing  it  more,  makes  you  beQer

���78

Page 103: Unit testing PHP apps with PHPUnit

#PHPBNL14 January 25 - 26, 2014

���80

phpcon.eu

Page 104: Unit testing PHP apps with PHPUnit

���81

https://joind.in/10113

If you liked it, thank you! If not, tell me how to improve this talk

Page 105: Unit testing PHP apps with PHPUnit

���82

Michelangelo van Dam Zend Certified Engineer !

[email protected]

PHP Consulting - QA audits - Training !

www.in2it.be

Page 106: Unit testing PHP apps with PHPUnit

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

Page 107: Unit testing PHP apps with PHPUnit

Thank  you

���84