SlideShare a Scribd company logo
1 of 107
Download to read offline
Day Camp
4 Developers

Day Camp
4 Developers

&

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	
  

-­‐

right,	
  like	
  that’s	
  going	
  to	
  happen	
  

• We	
  add	
  unit	
  tests	
  a@er	
  finish	
  project	
  
•…

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
My	
  example	
  code
• Get	
  this	
  example	
  from	
  GitHub	
  
-­‐

hQps://github.com/in2it/Utexamples

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

8
Simple	
  example	
  class
• Add	
  by	
  one	
  

-­‐ adds	
  the	
  current	
  value	
  by	
  one

?php !
namespace Utexamples; !

!

/** !
 * Class that allows us to make all sorts of calculations !
 */ !
class Calculator !
{ !
    protected $_value = 0; !

!

    /** !
     * Adds the current value by one !
     * !
     * @return int The value from this method !
     */ !
    public function addByOne() !
    { !
        $this-_value++; !
        return $this-_value; !
    } !
}
9
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
Product
_productId : integer
_code : string
_title : string
_description : string
_image : string
_price : float
_created : DateTime
_modified : DateTime
__construct($params : null | array)
setProductId($productId : integer) : Product
getProductId() : integer
setCode($code : string) : Product
getCode() : string
setTitle($title : string) : Product
getTitle() : string
setDescription($description : string) : Product
getDescription() : string
setImage($image : string) : Product
getImage() : string
setPrice($price : float) : Product
getPrice() : float
setCreated($created : string | DateTime) : Product
getCreated() : DateTime
setModified($modified : string | DateTime) : Product
getModified() : DateTime
populate($data : array)
toArray() : array
__toString() : string
21
A	
  simple	
  ProductTest
?php !
namespace UtexamplesModel; !
/** !
 * Class ProductTest !
 * @package UtexamplesModel !
 * @group Model !
 */ !
class ProductTest extends PHPUnit_Framework_TestCase !
{ !
    public function testProductCanBePopulated() !
    { !
        $data = array ( !
            'productId' = 1, !
            'code' = 'TSTPROD1', !
            'title' = 'Test product 1', !
            'description' = 'This is a full description of test product 1', !
            'image' = 'image.png', !
            'price' = 123.95, !
            'created' = '2013-11-20 16:00:00', !
            'modified' = '2013-11-20 17:00:00', !
        ); !

!

        $product = new Product($data); !
        $this-assertEquals($data, $product-toArray()); !
    } !
}
22
A	
  simple	
  ProductTest
?php !
namespace UtexamplesModel; !
/** !
 * Class ProductTest !
 * @package UtexamplesModel !
 * @group Model !
 */ !
class ProductTest extends PHPUnit_Framework_TestCase !
{ !
    public function testProductCanBePopulated() !
    { !
        $data = array ( !
            'productId' = 1, !
            'code' = 'TSTPROD1', !
            'title' = 'Test product 1', !
            'description' = 'This is a full description of test product 1', !
            'image' = 'image.png', !
            'price' = 123.95, !
            'created' = '2013-11-20 16:00:00', !
            'modified' = '2013-11-20 17:00:00', !
        ); !

!
        $product = new Product($data); !
        $product = new Product($data); !
        $this-assertEquals($data, $product-toArray());
        $this-assertEquals($data, $product-toArray()); !
    } !
}
22
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 !
     * @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
Running	
  with	
  @dataProvider

26
Running	
  with	
  @dataProvider

26
To	
  protect	
  and	
  to	
  serve

27
OWASP	
  top	
  10	
  exploits

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

29
Libs	
  you	
  can	
  use
• Zend	
  Framework	
  1:	
  Zend_Filter_Input	
  
• Zend	
  Framework	
  2:	
  ZendInputFilter	
  
• Symfony:	
  SymfonyComponentValidator	
  
• Aura:	
  AuraFrameworkInputFilter	
  
• Lithium:	
  lithiumu6lValidator	
  
• Laravel:	
  AppValidator
30
Modify	
  our	
  Product	
  class
?php !
namespace UtexamplesModel; !

!

class Product extends ModelAbstract !
{ !
...!
    /** !
     * @var Zend_Filter_Input The filter/validator for this Product !
     */ !
    protected $_inputFilter; !

!

    /** !
     * @var bool The validation of data for this Product !
     */ !
    protected $_valid; !

!

    /** !
     * Helper class to create filter and validation rules !
     * !
     * @access protected !
     */ !
    protected function _createInputFilter() !
    { !
...!
    } !
...!
}

31
_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-_inputFilter-setData(array ('code' = $code)); !
            $this-_code = $this-_inputFilter-code; !
        if ($this-_inputFilter-isValid('code')) { !
            $this-_code = $this-_inputFilter-code; !
            $this-setValid(true); !
            $this-setValid(true); !
        } else { !
        } else { !
            $this-setValid(false); !
            $this-setValid(false); !
        } ! !
        }
        return $this; !
        return $this;
    }

!

33
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); !
        $product = new Product($data); !
        $this-assertFalse($product-isValid()); !
        $this-assertFalse($product-isValid());
    }

35
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 UtexamplesModel; !

!

use PHPUnit_Extensions_Database_DataSet_IDataSet; !
use PHPUnit_Extensions_Database_DB_IDatabaseConnection; !
use PHPUnit_Extensions_Database_DataSet_QueryDataSet; !

!

class ProductDbTest extends PHPUnit_Extensions_Database_TestCase !
{ !
    protected $_pdo; !

!

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

!

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

!

    protected function getDataSet() !
    { !
        return $this-createFlatXMLDataSet(dirname(__DIR__) . '/_files/initialDataSet.xml'); !
    } !
}
40
Hello	
  DBUnit
?php !
namespace UtexamplesModel; !

!

use PHPUnit_Extensions_Database_DataSet_IDataSet; !
use PHPUnit_Extensions_Database_DB_IDatabaseConnection; !
use PHPUnit_Extensions_Database_DataSet_QueryDataSet; !

!

class ProductDbTest extends PHPUnit_Extensions_Database_TestCase !
{ !
    protected $_pdo; !

protected $_pdo; !
!
!
    public function __construct()

!

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

!

); !

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

!

    protected function getDataSet() !
    { !
        return $this-createFlatXMLDataSet(dirname(__DIR__) . '/_files/initialDataSet.xml'); !
    } !
}
40
Hello	
  DBUnit
?php !
namespace UtexamplesModel; !

!

use PHPUnit_Extensions_Database_DataSet_IDataSet; !
use PHPUnit_Extensions_Database_DB_IDatabaseConnection; !
use PHPUnit_Extensions_Database_DataSet_QueryDataSet; !

!

class ProductDbTest extends PHPUnit_Extensions_Database_TestCase !
{ !
    protected $_pdo; !

!

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

{
!

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

!

    protected function getDataSet() !
    { !
        return $this-createFlatXMLDataSet(dirname(__DIR__) . '/_files/initialDataSet.xml'); !
    } !
}
40
Hello	
  DBUnit
?php !
namespace UtexamplesModel; !

!

use PHPUnit_Extensions_Database_DataSet_IDataSet; !
use PHPUnit_Extensions_Database_DB_IDatabaseConnection; !
use PHPUnit_Extensions_Database_DataSet_QueryDataSet; !

!

class ProductDbTest extends PHPUnit_Extensions_Database_TestCase !
{ !
    protected $_pdo; !

!

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

!

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

    return $this-createFlatXMLDataSet(!
!

dirname(__DIR__) . ‘/_files/initialDataSet.xml'!
    protected function getDataSet() !
    {); !
!
        return $this-createFlatXMLDataSet(dirname(__DIR__) . '/_files/initialDataSet.xml'); !
}
    } !
}
40
Hello	
  DBUnit
?php !
namespace UtexamplesModel; !

!

use PHPUnit_Extensions_Database_DataSet_IDataSet; !
use PHPUnit_Extensions_Database_DB_IDatabaseConnection; !
use PHPUnit_Extensions_Database_DataSet_QueryDataSet; !

!

class ProductDbTest extends PHPUnit_Extensions_Database_TestCase !
{ !
    protected $_pdo; !

!

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

!

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

    return $this-createFlatXMLDataSet(!
!

dirname(__DIR__) . ‘/_files/initialDataSet.xml'!
    protected function getDataSet() !
    {); !
!
        return $this-createFlatXMLDataSet(dirname(__DIR__) . '/_files/initialDataSet.xml'); !
}
    } !
}
40
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
$data = array ( !
modified=2013-12-20 16:00:00”/
    'code' = 'TST', !
/dataset
    'title' = 'Test', !
    'description' = 'Testing Test', !
    'image' = 'http://www.example.com/image.png', !
    'price' = 10.00, !
    'created' = '2013-12-15 01:55:00', !
    'modified' = '2013-12-20 16:00:00', !
);

48
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	
  

-­‐
-­‐
-­‐

databases	
  
web	
  services	
  
file	
  systems	
  

-­‐

once	
  you	
  got	
  everything	
  set	
  up

• Since	
  they	
  replace	
  “expansive”	
  connec6ons	
  
• Are	
  quicker	
  and	
  more	
  reliable	
  to	
  test	
  
53
Same	
  tests,	
  but	
  now	
  mocked
?php !

!

namespace UtexamplesModel; !

!

use PDO; !
use PDOStatement; !

!

class ProductMockTest extends PHPUnit_Framework_TestCase !
{ !
    public function testProductsCanBeLoadedFromDatabase() !
    { !

!

    } !

!

    public function testProductAddToDatabase() !
    { !

!

    } !
}

54
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

Fork	
  it:	
  hQps://github.com/ThijsFeryn/talks.feryn.eu
62
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?responseitemusernameDragonBe/
usernamefull_nameMichelangelo van Dam/full_nameID19/IDlast_login1303248639/
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?responsedt' . $date-format('r') . '/
dttest_stringtesting 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
@@ @@
ID19/ID
last_login1303248639/last_login
+
last_login1303250271/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
- dtTue, 19 Apr 2011 22:26:40 +0000/dt
+ dtTue, 19 Apr 2011 22:26:41 +0000/dt
test_stringtesting 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());
        $client-setAdapter(new Zend_Http_Client_Adapter_Test()); !
$this-_joindin-setClient($client); !
        $this-_joindin-setClient($client); !
        $settings = simplexml_load_file(realpath( !
$settings = simplexml_load_file(realpath( !
            APPLICATION_PATH . '/../tests/_files/settings.xml')); !
APPLICATION_PATH . '/../tests/_files/settings.xml'));
        $this-_settings = $settings-joindin; !
        parent::setUp(); !
    } !
    protected function tearDown() !
    { !
        parent::tearDown(); !
        $this-_joindin = null; !
    } !
}

!

70
Mocking	
  the	
  response
public function testJoindinCanGetUserDetails() !
{ !
    $response = EOS !
HTTP/1.1 200 OK !
Content-type: text/xml !

!

?xml version=1.0? !
response !
  item !
    usernameDragonBe/username !
    full_nameMichelangelo van Dam/full_name !
    ID19/ID !
    last_login1303248639/last_login !
  /item !
/response         !
EOS; !
    $client = $this-_joindin-getClient()-getAdapter()-setResponse($response); !
    $expected = '?xml version=1.0?responseitemusernameDragonBe/
usernamefull_nameMichelangelo van Dam/full_nameID19/IDlast_login1303248639/
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	
  response
public function testJoindinCanGetUserDetails() !
{ !
    $response = EOS !
HTTP/1.1 200 OK !
Content-type: text/xml !

!

?xml version=1.0? !
response !
  item !
    usernameDragonBe/username !
    full_nameMichelangelo van Dam/full_name !
    ID19/ID !
    last_login1303248639/last_login !
  /item !
/response         !
EOS; !
$client = $this-_joindin-getClient()-getAdapter()-setResponse($response);
    $client = $this-_joindin-getClient()-getAdapter()-setResponse($response); !
    $expected = '?xml version=1.0?responseitemusernameDragonBe/
usernamefull_nameMichelangelo van Dam/full_nameID19/IDlast_login1303248639/
last_login/item/response'; !
    $this-_joindin-setUsername($this-_settings-username) !
                   -setPassword($this-_settings-password); !
    $actual = $this-_joindin-user()-getDetail(); !
    $this-assertXmlStringEqualsXmlString($expected, $actual); !
}

71
Same	
  here
public function testJoindinCanCheckStatus() !
{ !
    $date = new DateTime(); !
    $date-setTimezone(new DateTimeZone('UTC')); !
    $response = EOS !
HTTP/1.1 200 OK !
Content-type: text/xml !

!

?xml version=1.0? !
response !
  dt{$date-format('r')}/dt !
  test_stringtesting unit test/test_string !
/response         !
EOS; !

!

    $client = $this-_joindin-getClient() !
                             -getAdapter()-setResponse($response);!
!
    $expected = '?xml version=1.0?responsedt' . $date-format('r') . '/
dttest_stringtesting unit test/test_string/response'; !
    $actual = $this-_joindin-site()-getStatus('testing unit test'); !
    $this-assertXmlStringEqualsXmlString($expected, $actual); !
}

72
Same	
  here
public function testJoindinCanCheckStatus() !
{ !
    $date = new DateTime(); !
    $date-setTimezone(new DateTimeZone('UTC')); !
    $response = EOS !
HTTP/1.1 200 OK !
Content-type: text/xml !

!

?xml version=1.0? !
response !
  dt{$date-format('r')}/dt !
  test_stringtesting unit test/test_string !
/response         !
EOS; !

!

$client = $this-_joindin-getClient() !
    $client = $this-_joindin-getClient() !
                         -getAdapter()-setResponse($response);
                             -getAdapter()-setResponse($response);!
!
    $expected = '?xml version=1.0?responsedt' . $date-format('r') . '/
dttest_stringtesting unit test/test_string/response'; !
    $actual = $this-_joindin-site()-getStatus('testing unit test'); !
    $this-assertXmlStringEqualsXmlString($expected, $actual); !
}

72
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
Recommended	
  reading
Click on the images to view

www.owasp.org

planet.phpunit.de
79
#PHPBNL14
January 25 - 26, 2014

phpcon.eu

80
https://joind.in/10113
If you liked it, thank you!	

If not, tell me how to improve this talk
81
Michelangelo van Dam
Zend Certified Engineer	

!

michelangelo@in2it.be

PHP Consulting - QA audits - Training	

!

www.in2it.be
82
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

More Related Content

What's hot

Advanced PHPUnit Testing
Advanced PHPUnit TestingAdvanced PHPUnit Testing
Advanced PHPUnit TestingMike Lively
 
PhpUnit Best Practices
PhpUnit Best PracticesPhpUnit Best Practices
PhpUnit Best PracticesEdorian
 
Test your code like a pro - PHPUnit in practice
Test your code like a pro - PHPUnit in practiceTest your code like a pro - PHPUnit in practice
Test your code like a pro - PHPUnit in practiceSebastian Marek
 
Workshop quality assurance for php projects - ZendCon 2013
Workshop quality assurance for php projects - ZendCon 2013Workshop quality assurance for php projects - ZendCon 2013
Workshop quality assurance for php projects - ZendCon 2013Michelangelo van Dam
 
UA testing with Selenium and PHPUnit - PFCongres 2013
UA testing with Selenium and PHPUnit - PFCongres 2013UA testing with Selenium and PHPUnit - PFCongres 2013
UA testing with Selenium and PHPUnit - PFCongres 2013Michelangelo van Dam
 
Beginning PHPUnit
Beginning PHPUnitBeginning PHPUnit
Beginning PHPUnitJace Ju
 
Unit Testng with PHP Unit - A Step by Step Training
Unit Testng with PHP Unit - A Step by Step TrainingUnit Testng with PHP Unit - A Step by Step Training
Unit Testng with PHP Unit - A Step by Step TrainingRam Awadh Prasad, PMP
 
Mocking Dependencies in PHPUnit
Mocking Dependencies in PHPUnitMocking Dependencies in PHPUnit
Mocking Dependencies in PHPUnitmfrost503
 
Building Testable PHP Applications
Building Testable PHP ApplicationsBuilding Testable PHP Applications
Building Testable PHP Applicationschartjes
 
EPHPC Webinar Slides: Unit Testing by Arthur Purnama
EPHPC Webinar Slides: Unit Testing by Arthur PurnamaEPHPC Webinar Slides: Unit Testing by Arthur Purnama
EPHPC Webinar Slides: Unit Testing by Arthur PurnamaEnterprise PHP Center
 
Effective testing with pytest
Effective testing with pytestEffective testing with pytest
Effective testing with pytestHector Canto
 
Keep your repo clean
Keep your repo cleanKeep your repo clean
Keep your repo cleanHector Canto
 
Unlock The Mystery Of PHPUnit (Wave PHP 2018)
Unlock The Mystery Of PHPUnit (Wave PHP 2018)Unlock The Mystery Of PHPUnit (Wave PHP 2018)
Unlock The Mystery Of PHPUnit (Wave PHP 2018)ENDelt260
 
Test in action week 2
Test in action   week 2Test in action   week 2
Test in action week 2Yi-Huan Chan
 
Python testing using mock and pytest
Python testing using mock and pytestPython testing using mock and pytest
Python testing using mock and pytestSuraj Deshmukh
 

What's hot (20)

Advanced PHPUnit Testing
Advanced PHPUnit TestingAdvanced PHPUnit Testing
Advanced PHPUnit Testing
 
Phpunit testing
Phpunit testingPhpunit testing
Phpunit testing
 
PhpUnit Best Practices
PhpUnit Best PracticesPhpUnit Best Practices
PhpUnit Best Practices
 
PHPUnit
PHPUnitPHPUnit
PHPUnit
 
Test your code like a pro - PHPUnit in practice
Test your code like a pro - PHPUnit in practiceTest your code like a pro - PHPUnit in practice
Test your code like a pro - PHPUnit in practice
 
Workshop quality assurance for php projects - ZendCon 2013
Workshop quality assurance for php projects - ZendCon 2013Workshop quality assurance for php projects - ZendCon 2013
Workshop quality assurance for php projects - ZendCon 2013
 
UA testing with Selenium and PHPUnit - PFCongres 2013
UA testing with Selenium and PHPUnit - PFCongres 2013UA testing with Selenium and PHPUnit - PFCongres 2013
UA testing with Selenium and PHPUnit - PFCongres 2013
 
Beginning PHPUnit
Beginning PHPUnitBeginning PHPUnit
Beginning PHPUnit
 
Unit Testng with PHP Unit - A Step by Step Training
Unit Testng with PHP Unit - A Step by Step TrainingUnit Testng with PHP Unit - A Step by Step Training
Unit Testng with PHP Unit - A Step by Step Training
 
Mocking Dependencies in PHPUnit
Mocking Dependencies in PHPUnitMocking Dependencies in PHPUnit
Mocking Dependencies in PHPUnit
 
Building Testable PHP Applications
Building Testable PHP ApplicationsBuilding Testable PHP Applications
Building Testable PHP Applications
 
EPHPC Webinar Slides: Unit Testing by Arthur Purnama
EPHPC Webinar Slides: Unit Testing by Arthur PurnamaEPHPC Webinar Slides: Unit Testing by Arthur Purnama
EPHPC Webinar Slides: Unit Testing by Arthur Purnama
 
Effective testing with pytest
Effective testing with pytestEffective testing with pytest
Effective testing with pytest
 
Unit testing
Unit testingUnit testing
Unit testing
 
Keep your repo clean
Keep your repo cleanKeep your repo clean
Keep your repo clean
 
Unlock The Mystery Of PHPUnit (Wave PHP 2018)
Unlock The Mystery Of PHPUnit (Wave PHP 2018)Unlock The Mystery Of PHPUnit (Wave PHP 2018)
Unlock The Mystery Of PHPUnit (Wave PHP 2018)
 
Test in action week 2
Test in action   week 2Test in action   week 2
Test in action week 2
 
PHPUnit testing to Zend_Test
PHPUnit testing to Zend_TestPHPUnit testing to Zend_Test
PHPUnit testing to Zend_Test
 
Laravel Unit Testing
Laravel Unit TestingLaravel Unit Testing
Laravel Unit Testing
 
Python testing using mock and pytest
Python testing using mock and pytestPython testing using mock and pytest
Python testing using mock and pytest
 

Viewers also liked

Integracion Cakephp y Bootstrap
Integracion Cakephp y BootstrapIntegracion Cakephp y Bootstrap
Integracion Cakephp y BootstrapDeltron
 
Herramientas Web 1.0,2.0 Y 3.0
Herramientas Web 1.0,2.0 Y 3.0Herramientas Web 1.0,2.0 Y 3.0
Herramientas Web 1.0,2.0 Y 3.0Daniel Leffa
 
Continuous delivery met jenkins twist en puppet
Continuous delivery met jenkins twist en puppetContinuous delivery met jenkins twist en puppet
Continuous delivery met jenkins twist en puppetltebbens
 

Viewers also liked (8)

CakePHP Grandes Empresas
CakePHP Grandes EmpresasCakePHP Grandes Empresas
CakePHP Grandes Empresas
 
Integracion Cakephp y Bootstrap
Integracion Cakephp y BootstrapIntegracion Cakephp y Bootstrap
Integracion Cakephp y Bootstrap
 
Herramientas Web 1.0,2.0 Y 3.0
Herramientas Web 1.0,2.0 Y 3.0Herramientas Web 1.0,2.0 Y 3.0
Herramientas Web 1.0,2.0 Y 3.0
 
Iniciación Con CakePHP
Iniciación Con CakePHPIniciación Con CakePHP
Iniciación Con CakePHP
 
Especiacion2
Especiacion2Especiacion2
Especiacion2
 
Continuous delivery met jenkins twist en puppet
Continuous delivery met jenkins twist en puppetContinuous delivery met jenkins twist en puppet
Continuous delivery met jenkins twist en puppet
 
Continuous deployment 2.0
Continuous deployment 2.0Continuous deployment 2.0
Continuous deployment 2.0
 
Continuous Quality Assurance
Continuous Quality AssuranceContinuous Quality Assurance
Continuous Quality Assurance
 

Similar to Unit testing PHP apps with PHPUnit

Leveling Up With Unit Testing - php[tek] 2023
Leveling Up With Unit Testing - php[tek] 2023Leveling Up With Unit Testing - php[tek] 2023
Leveling Up With Unit Testing - php[tek] 2023Mark Niebergall
 
Developer testing 101: Become a Testing Fanatic
Developer testing 101: Become a Testing FanaticDeveloper testing 101: Become a Testing Fanatic
Developer testing 101: Become a Testing FanaticLB Denker
 
Workshop quality assurance for php projects tek12
Workshop quality assurance for php projects tek12Workshop quality assurance for php projects tek12
Workshop quality assurance for php projects tek12Michelangelo van Dam
 
Test Driven Development with JavaFX
Test Driven Development with JavaFXTest Driven Development with JavaFX
Test Driven Development with JavaFXHendrik Ebbers
 
Twig, the flexible, fast, and secure template language for PHP
Twig, the flexible, fast, and secure template language for PHPTwig, the flexible, fast, and secure template language for PHP
Twig, the flexible, fast, and secure template language for PHPFabien Potencier
 
Bring the fun back to java
Bring the fun back to javaBring the fun back to java
Bring the fun back to javaciklum_ods
 
Quality Assurance for PHP projects - ZendCon 2012
Quality Assurance for PHP projects - ZendCon 2012Quality Assurance for PHP projects - ZendCon 2012
Quality Assurance for PHP projects - ZendCon 2012Michelangelo van Dam
 
Leveling Up With Unit Testing - LonghornPHP 2022
Leveling Up With Unit Testing - LonghornPHP 2022Leveling Up With Unit Testing - LonghornPHP 2022
Leveling Up With Unit Testing - LonghornPHP 2022Mark Niebergall
 
Continuous integration with Git & CI Joe
Continuous integration with Git & CI JoeContinuous integration with Git & CI Joe
Continuous integration with Git & CI JoeShawn Price
 
Milot Shala - C++ (OSCAL2014)
Milot Shala - C++ (OSCAL2014)Milot Shala - C++ (OSCAL2014)
Milot Shala - C++ (OSCAL2014)Open Labs Albania
 
Boost delivery stream with code discipline engineering
Boost delivery stream with code discipline engineeringBoost delivery stream with code discipline engineering
Boost delivery stream with code discipline engineeringMiro Wengner
 
New Ideas for Old Code - Greach
New Ideas for Old Code - GreachNew Ideas for Old Code - Greach
New Ideas for Old Code - GreachHamletDRC
 
Building Potent WordPress Websites
Building Potent WordPress WebsitesBuilding Potent WordPress Websites
Building Potent WordPress WebsitesKyle Cearley
 
Workshop quality assurance for php projects - phpdublin
Workshop quality assurance for php projects - phpdublinWorkshop quality assurance for php projects - phpdublin
Workshop quality assurance for php projects - phpdublinMichelangelo van Dam
 
Extracting Plugins And Gems From Rails Apps
Extracting Plugins And Gems From Rails AppsExtracting Plugins And Gems From Rails Apps
Extracting Plugins And Gems From Rails AppsJosh Nichols
 
Refactoring In Tdd The Missing Part
Refactoring In Tdd The Missing PartRefactoring In Tdd The Missing Part
Refactoring In Tdd The Missing PartGabriele Lana
 
What's new in PHP 8.0?
What's new in PHP 8.0?What's new in PHP 8.0?
What's new in PHP 8.0?Nikita Popov
 
Nikita Popov "What’s new in PHP 8.0?"
Nikita Popov "What’s new in PHP 8.0?"Nikita Popov "What’s new in PHP 8.0?"
Nikita Popov "What’s new in PHP 8.0?"Fwdays
 

Similar to Unit testing PHP apps with PHPUnit (20)

Leveling Up With Unit Testing - php[tek] 2023
Leveling Up With Unit Testing - php[tek] 2023Leveling Up With Unit Testing - php[tek] 2023
Leveling Up With Unit Testing - php[tek] 2023
 
Developer testing 101: Become a Testing Fanatic
Developer testing 101: Become a Testing FanaticDeveloper testing 101: Become a Testing Fanatic
Developer testing 101: Become a Testing Fanatic
 
Workshop quality assurance for php projects tek12
Workshop quality assurance for php projects tek12Workshop quality assurance for php projects tek12
Workshop quality assurance for php projects tek12
 
Test Driven Development with JavaFX
Test Driven Development with JavaFXTest Driven Development with JavaFX
Test Driven Development with JavaFX
 
2009-02 Oops!
2009-02 Oops!2009-02 Oops!
2009-02 Oops!
 
Twig, the flexible, fast, and secure template language for PHP
Twig, the flexible, fast, and secure template language for PHPTwig, the flexible, fast, and secure template language for PHP
Twig, the flexible, fast, and secure template language for PHP
 
Bring the fun back to java
Bring the fun back to javaBring the fun back to java
Bring the fun back to java
 
Quality Assurance for PHP projects - ZendCon 2012
Quality Assurance for PHP projects - ZendCon 2012Quality Assurance for PHP projects - ZendCon 2012
Quality Assurance for PHP projects - ZendCon 2012
 
Tango with django
Tango with djangoTango with django
Tango with django
 
Leveling Up With Unit Testing - LonghornPHP 2022
Leveling Up With Unit Testing - LonghornPHP 2022Leveling Up With Unit Testing - LonghornPHP 2022
Leveling Up With Unit Testing - LonghornPHP 2022
 
Continuous integration with Git & CI Joe
Continuous integration with Git & CI JoeContinuous integration with Git & CI Joe
Continuous integration with Git & CI Joe
 
Milot Shala - C++ (OSCAL2014)
Milot Shala - C++ (OSCAL2014)Milot Shala - C++ (OSCAL2014)
Milot Shala - C++ (OSCAL2014)
 
Boost delivery stream with code discipline engineering
Boost delivery stream with code discipline engineeringBoost delivery stream with code discipline engineering
Boost delivery stream with code discipline engineering
 
New Ideas for Old Code - Greach
New Ideas for Old Code - GreachNew Ideas for Old Code - Greach
New Ideas for Old Code - Greach
 
Building Potent WordPress Websites
Building Potent WordPress WebsitesBuilding Potent WordPress Websites
Building Potent WordPress Websites
 
Workshop quality assurance for php projects - phpdublin
Workshop quality assurance for php projects - phpdublinWorkshop quality assurance for php projects - phpdublin
Workshop quality assurance for php projects - phpdublin
 
Extracting Plugins And Gems From Rails Apps
Extracting Plugins And Gems From Rails AppsExtracting Plugins And Gems From Rails Apps
Extracting Plugins And Gems From Rails Apps
 
Refactoring In Tdd The Missing Part
Refactoring In Tdd The Missing PartRefactoring In Tdd The Missing Part
Refactoring In Tdd The Missing Part
 
What's new in PHP 8.0?
What's new in PHP 8.0?What's new in PHP 8.0?
What's new in PHP 8.0?
 
Nikita Popov "What’s new in PHP 8.0?"
Nikita Popov "What’s new in PHP 8.0?"Nikita Popov "What’s new in PHP 8.0?"
Nikita Popov "What’s new in PHP 8.0?"
 

More from Michelangelo van Dam

GDPR Art. 25 - Privacy by design and default
GDPR Art. 25 - Privacy by design and defaultGDPR Art. 25 - Privacy by design and default
GDPR Art. 25 - Privacy by design and defaultMichelangelo van Dam
 
Moving from app services to azure functions
Moving from app services to azure functionsMoving from app services to azure functions
Moving from app services to azure functionsMichelangelo van Dam
 
General Data Protection Regulation, a developer's story
General Data Protection Regulation, a developer's storyGeneral Data Protection Regulation, a developer's story
General Data Protection Regulation, a developer's storyMichelangelo van Dam
 
Leveraging a distributed architecture to your advantage
Leveraging a distributed architecture to your advantageLeveraging a distributed architecture to your advantage
Leveraging a distributed architecture to your advantageMichelangelo van Dam
 
Open source for a successful business
Open source for a successful businessOpen source for a successful business
Open source for a successful businessMichelangelo van Dam
 
Decouple your framework now, thank me later
Decouple your framework now, thank me laterDecouple your framework now, thank me later
Decouple your framework now, thank me laterMichelangelo van Dam
 
Deploy to azure in less then 15 minutes
Deploy to azure in less then 15 minutesDeploy to azure in less then 15 minutes
Deploy to azure in less then 15 minutesMichelangelo van Dam
 
Azure and OSS, a match made in heaven
Azure and OSS, a match made in heavenAzure and OSS, a match made in heaven
Azure and OSS, a match made in heavenMichelangelo van Dam
 
Zf2 how arrays will save your project
Zf2   how arrays will save your projectZf2   how arrays will save your project
Zf2 how arrays will save your projectMichelangelo van Dam
 
PHPUnit Episode iv.iii: Return of the tests
PHPUnit Episode iv.iii: Return of the testsPHPUnit Episode iv.iii: Return of the tests
PHPUnit Episode iv.iii: Return of the testsMichelangelo van Dam
 
Easily extend your existing php app with an api
Easily extend your existing php app with an apiEasily extend your existing php app with an api
Easily extend your existing php app with an apiMichelangelo van Dam
 

More from Michelangelo van Dam (20)

GDPR Art. 25 - Privacy by design and default
GDPR Art. 25 - Privacy by design and defaultGDPR Art. 25 - Privacy by design and default
GDPR Art. 25 - Privacy by design and default
 
Moving from app services to azure functions
Moving from app services to azure functionsMoving from app services to azure functions
Moving from app services to azure functions
 
Privacy by design
Privacy by designPrivacy by design
Privacy by design
 
DevOps or DevSecOps
DevOps or DevSecOpsDevOps or DevSecOps
DevOps or DevSecOps
 
Privacy by design
Privacy by designPrivacy by design
Privacy by design
 
Let your tests drive your code
Let your tests drive your codeLet your tests drive your code
Let your tests drive your code
 
General Data Protection Regulation, a developer's story
General Data Protection Regulation, a developer's storyGeneral Data Protection Regulation, a developer's story
General Data Protection Regulation, a developer's story
 
Leveraging a distributed architecture to your advantage
Leveraging a distributed architecture to your advantageLeveraging a distributed architecture to your advantage
Leveraging a distributed architecture to your advantage
 
The road to php 7.1
The road to php 7.1The road to php 7.1
The road to php 7.1
 
Open source for a successful business
Open source for a successful businessOpen source for a successful business
Open source for a successful business
 
Decouple your framework now, thank me later
Decouple your framework now, thank me laterDecouple your framework now, thank me later
Decouple your framework now, thank me later
 
Deploy to azure in less then 15 minutes
Deploy to azure in less then 15 minutesDeploy to azure in less then 15 minutes
Deploy to azure in less then 15 minutes
 
Azure and OSS, a match made in heaven
Azure and OSS, a match made in heavenAzure and OSS, a match made in heaven
Azure and OSS, a match made in heaven
 
Getting hands dirty with php7
Getting hands dirty with php7Getting hands dirty with php7
Getting hands dirty with php7
 
Zf2 how arrays will save your project
Zf2   how arrays will save your projectZf2   how arrays will save your project
Zf2 how arrays will save your project
 
Create, test, secure, repeat
Create, test, secure, repeatCreate, test, secure, repeat
Create, test, secure, repeat
 
The Continuous PHP Pipeline
The Continuous PHP PipelineThe Continuous PHP Pipeline
The Continuous PHP Pipeline
 
PHPUnit Episode iv.iii: Return of the tests
PHPUnit Episode iv.iii: Return of the testsPHPUnit Episode iv.iii: Return of the tests
PHPUnit Episode iv.iii: Return of the tests
 
Easily extend your existing php app with an api
Easily extend your existing php app with an apiEasily extend your existing php app with an api
Easily extend your existing php app with an api
 
Your code are my tests
Your code are my testsYour code are my tests
Your code are my tests
 

Recently uploaded

Time Series Foundation Models - current state and future directions
Time Series Foundation Models - current state and future directionsTime Series Foundation Models - current state and future directions
Time Series Foundation Models - current state and future directionsNathaniel Shimoni
 
Rise of the Machines: Known As Drones...
Rise of the Machines: Known As Drones...Rise of the Machines: Known As Drones...
Rise of the Machines: Known As Drones...Rick Flair
 
A Deep Dive on Passkeys: FIDO Paris Seminar.pptx
A Deep Dive on Passkeys: FIDO Paris Seminar.pptxA Deep Dive on Passkeys: FIDO Paris Seminar.pptx
A Deep Dive on Passkeys: FIDO Paris Seminar.pptxLoriGlavin3
 
Ryan Mahoney - Will Artificial Intelligence Replace Real Estate Agents
Ryan Mahoney - Will Artificial Intelligence Replace Real Estate AgentsRyan Mahoney - Will Artificial Intelligence Replace Real Estate Agents
Ryan Mahoney - Will Artificial Intelligence Replace Real Estate AgentsRyan Mahoney
 
TrustArc Webinar - How to Build Consumer Trust Through Data Privacy
TrustArc Webinar - How to Build Consumer Trust Through Data PrivacyTrustArc Webinar - How to Build Consumer Trust Through Data Privacy
TrustArc Webinar - How to Build Consumer Trust Through Data PrivacyTrustArc
 
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek SchlawackFwdays
 
SIP trunking in Janus @ Kamailio World 2024
SIP trunking in Janus @ Kamailio World 2024SIP trunking in Janus @ Kamailio World 2024
SIP trunking in Janus @ Kamailio World 2024Lorenzo Miniero
 
Nell’iperspazio con Rocket: il Framework Web di Rust!
Nell’iperspazio con Rocket: il Framework Web di Rust!Nell’iperspazio con Rocket: il Framework Web di Rust!
Nell’iperspazio con Rocket: il Framework Web di Rust!Commit University
 
Anypoint Exchange: It’s Not Just a Repo!
Anypoint Exchange: It’s Not Just a Repo!Anypoint Exchange: It’s Not Just a Repo!
Anypoint Exchange: It’s Not Just a Repo!Manik S Magar
 
Sample pptx for embedding into website for demo
Sample pptx for embedding into website for demoSample pptx for embedding into website for demo
Sample pptx for embedding into website for demoHarshalMandlekar2
 
A Journey Into the Emotions of Software Developers
A Journey Into the Emotions of Software DevelopersA Journey Into the Emotions of Software Developers
A Journey Into the Emotions of Software DevelopersNicole Novielli
 
DevoxxFR 2024 Reproducible Builds with Apache Maven
DevoxxFR 2024 Reproducible Builds with Apache MavenDevoxxFR 2024 Reproducible Builds with Apache Maven
DevoxxFR 2024 Reproducible Builds with Apache MavenHervé Boutemy
 
SALESFORCE EDUCATION CLOUD | FEXLE SERVICES
SALESFORCE EDUCATION CLOUD | FEXLE SERVICESSALESFORCE EDUCATION CLOUD | FEXLE SERVICES
SALESFORCE EDUCATION CLOUD | FEXLE SERVICESmohitsingh558521
 
"Debugging python applications inside k8s environment", Andrii Soldatenko
"Debugging python applications inside k8s environment", Andrii Soldatenko"Debugging python applications inside k8s environment", Andrii Soldatenko
"Debugging python applications inside k8s environment", Andrii SoldatenkoFwdays
 
Moving Beyond Passwords: FIDO Paris Seminar.pdf
Moving Beyond Passwords: FIDO Paris Seminar.pdfMoving Beyond Passwords: FIDO Paris Seminar.pdf
Moving Beyond Passwords: FIDO Paris Seminar.pdfLoriGlavin3
 
DevEX - reference for building teams, processes, and platforms
DevEX - reference for building teams, processes, and platformsDevEX - reference for building teams, processes, and platforms
DevEX - reference for building teams, processes, and platformsSergiu Bodiu
 
The Role of FIDO in a Cyber Secure Netherlands: FIDO Paris Seminar.pptx
The Role of FIDO in a Cyber Secure Netherlands: FIDO Paris Seminar.pptxThe Role of FIDO in a Cyber Secure Netherlands: FIDO Paris Seminar.pptx
The Role of FIDO in a Cyber Secure Netherlands: FIDO Paris Seminar.pptxLoriGlavin3
 
Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)
Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)
Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)Mark Simos
 
From Family Reminiscence to Scholarly Archive .
From Family Reminiscence to Scholarly Archive .From Family Reminiscence to Scholarly Archive .
From Family Reminiscence to Scholarly Archive .Alan Dix
 
(How to Program) Paul Deitel, Harvey Deitel-Java How to Program, Early Object...
(How to Program) Paul Deitel, Harvey Deitel-Java How to Program, Early Object...(How to Program) Paul Deitel, Harvey Deitel-Java How to Program, Early Object...
(How to Program) Paul Deitel, Harvey Deitel-Java How to Program, Early Object...AliaaTarek5
 

Recently uploaded (20)

Time Series Foundation Models - current state and future directions
Time Series Foundation Models - current state and future directionsTime Series Foundation Models - current state and future directions
Time Series Foundation Models - current state and future directions
 
Rise of the Machines: Known As Drones...
Rise of the Machines: Known As Drones...Rise of the Machines: Known As Drones...
Rise of the Machines: Known As Drones...
 
A Deep Dive on Passkeys: FIDO Paris Seminar.pptx
A Deep Dive on Passkeys: FIDO Paris Seminar.pptxA Deep Dive on Passkeys: FIDO Paris Seminar.pptx
A Deep Dive on Passkeys: FIDO Paris Seminar.pptx
 
Ryan Mahoney - Will Artificial Intelligence Replace Real Estate Agents
Ryan Mahoney - Will Artificial Intelligence Replace Real Estate AgentsRyan Mahoney - Will Artificial Intelligence Replace Real Estate Agents
Ryan Mahoney - Will Artificial Intelligence Replace Real Estate Agents
 
TrustArc Webinar - How to Build Consumer Trust Through Data Privacy
TrustArc Webinar - How to Build Consumer Trust Through Data PrivacyTrustArc Webinar - How to Build Consumer Trust Through Data Privacy
TrustArc Webinar - How to Build Consumer Trust Through Data Privacy
 
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack
 
SIP trunking in Janus @ Kamailio World 2024
SIP trunking in Janus @ Kamailio World 2024SIP trunking in Janus @ Kamailio World 2024
SIP trunking in Janus @ Kamailio World 2024
 
Nell’iperspazio con Rocket: il Framework Web di Rust!
Nell’iperspazio con Rocket: il Framework Web di Rust!Nell’iperspazio con Rocket: il Framework Web di Rust!
Nell’iperspazio con Rocket: il Framework Web di Rust!
 
Anypoint Exchange: It’s Not Just a Repo!
Anypoint Exchange: It’s Not Just a Repo!Anypoint Exchange: It’s Not Just a Repo!
Anypoint Exchange: It’s Not Just a Repo!
 
Sample pptx for embedding into website for demo
Sample pptx for embedding into website for demoSample pptx for embedding into website for demo
Sample pptx for embedding into website for demo
 
A Journey Into the Emotions of Software Developers
A Journey Into the Emotions of Software DevelopersA Journey Into the Emotions of Software Developers
A Journey Into the Emotions of Software Developers
 
DevoxxFR 2024 Reproducible Builds with Apache Maven
DevoxxFR 2024 Reproducible Builds with Apache MavenDevoxxFR 2024 Reproducible Builds with Apache Maven
DevoxxFR 2024 Reproducible Builds with Apache Maven
 
SALESFORCE EDUCATION CLOUD | FEXLE SERVICES
SALESFORCE EDUCATION CLOUD | FEXLE SERVICESSALESFORCE EDUCATION CLOUD | FEXLE SERVICES
SALESFORCE EDUCATION CLOUD | FEXLE SERVICES
 
"Debugging python applications inside k8s environment", Andrii Soldatenko
"Debugging python applications inside k8s environment", Andrii Soldatenko"Debugging python applications inside k8s environment", Andrii Soldatenko
"Debugging python applications inside k8s environment", Andrii Soldatenko
 
Moving Beyond Passwords: FIDO Paris Seminar.pdf
Moving Beyond Passwords: FIDO Paris Seminar.pdfMoving Beyond Passwords: FIDO Paris Seminar.pdf
Moving Beyond Passwords: FIDO Paris Seminar.pdf
 
DevEX - reference for building teams, processes, and platforms
DevEX - reference for building teams, processes, and platformsDevEX - reference for building teams, processes, and platforms
DevEX - reference for building teams, processes, and platforms
 
The Role of FIDO in a Cyber Secure Netherlands: FIDO Paris Seminar.pptx
The Role of FIDO in a Cyber Secure Netherlands: FIDO Paris Seminar.pptxThe Role of FIDO in a Cyber Secure Netherlands: FIDO Paris Seminar.pptx
The Role of FIDO in a Cyber Secure Netherlands: FIDO Paris Seminar.pptx
 
Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)
Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)
Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)
 
From Family Reminiscence to Scholarly Archive .
From Family Reminiscence to Scholarly Archive .From Family Reminiscence to Scholarly Archive .
From Family Reminiscence to Scholarly Archive .
 
(How to Program) Paul Deitel, Harvey Deitel-Java How to Program, Early Object...
(How to Program) Paul Deitel, Harvey Deitel-Java How to Program, Early Object...(How to Program) Paul Deitel, Harvey Deitel-Java How to Program, Early Object...
(How to Program) Paul Deitel, Harvey Deitel-Java How to Program, Early Object...
 

Unit testing PHP apps with PHPUnit

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