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

Unit testing PHP apps with PHPUnit

Aug 20, 2015

Download

Technology

Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
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