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