unit testing php apps with phpunit

Post on 20-Aug-2015

7.968 Views

Category:

Technology

7 Downloads

Preview:

Click to see full reader

TRANSCRIPT

Day Camp4 Developers

Day Camp4 Developers

Day Camp4 Developers

Day Camp4 Developers

DC4D

2&

Unit Testing PHP apps with PHPUnit

Michelangelo  van  Dam

���2

Let’s  talk  about  tes6ng

���3

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

No  excuses

���5

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

How  to  get  started?

���7

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

<?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

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

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

Running  PHPUnit

���12

Running  PHPUnit

���12

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

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

Now  run  more  relaxed

���15

Now  run  more  relaxed

���15

We’re  no  grads  anymore

���16

Real  apps,  real  money

���17

���18

How  to  reach  the  top

���19

Data  Model  Tes6ng

���20

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

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

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());

Running  the  test

���23

Running  the  test

���23

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

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

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 !     */

Running  with  @dataProvider

���26

Running  with  @dataProvider

���26

To  protect  and  to  serve

���27

OWASP  top  10  exploits

���28

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

Filtering  &  Valida6on

���29

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

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

_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

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

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;

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

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

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());

Running  our  tests

���36

Running  our  tests

���36

Databases,  the  wisdom  fountain

���37

4  stages  of  database  tes6ng

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

���38

Data  fixture  data  set

���39

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

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

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

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

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

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

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

���42

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

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

Running  our  DBUnit  test

���45

Running  our  DBUnit  test

���45

Oops

���46

Oops

���46

Oops

���46

Oh  no,  I  made  a  TYPO!

���47

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

Running  our  DBUnit  test

���49

Running  our  DBUnit  test

���49

Everybody  happy

���50

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

We  can  do  beQer!

���52

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

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

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

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

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

Running  Data  Mocking

���58

Running  Data  Mocking

���58

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

Web  Services

���60

Example:  joind.in

���61

Joindin  Case:  talks.feryn.eu

���62

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

API  Docs  are  your  friend

���63

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

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

Running  the  test

���66

Running  the  test

���66

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

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

No  dispair,  we  help  you  out!

���69

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

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'));

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

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

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

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

Now  we’re  good

���73

Now  we’re  good

���73

Conclusion

���74

Tes6ng  is  easy

���75

Even  for  spaghen  code

���76

Posi6ve  &  Nega6ve  tests

���77

Doing  it  more,  makes  you  beQer

���78

#PHPBNL14 January 25 - 26, 2014

���80

phpcon.eu

���81

https://joind.in/10113

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

���82

Michelangelo van Dam Zend Certified Engineer !

michelangelo@in2it.be

PHP Consulting - QA audits - Training !

www.in2it.be

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

Thank  you

���84

top related