Your SlideShare is downloading. ×
0
Unit Testing after ZF 1.8
             Michelangelo van Dam
    PHPBenelux Meeting September 2010, Rijswijk (NL)
Michelangelo van Dam

• Independent Consultant
• Zend Certified Engineer (ZCE)
 - PHP 4 & PHP 5
 - Zend Framework
• Co-Foun...
This session


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




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




   http://www.slideshare.net/weierophinney/testing-zend-framework-applications
Zend Framework 1.8
Birth of Zend_Application
• bootstrapping an “app”
• works the same for any environment
• resources through methods (no re...
Types of tests
Unit Testing
•- smallest functional code snippet (unit)
     function or class method
•- aims to challenge logic
     prov...
Controller Testing
•- tests your (ZF) app
     is this url linked to this controller ?
•- detects early errors
    on fron...
Database Testing
•- Tests the functionality of your database
    referred to as “integration testing”
•- Checks if your da...
Application Testing
Setting things up
phpunit.xml
   <phpunit bootstrap="./TestHelper.php" colors="true">
   <!-- Version: $Id: phpunit.xml 298 2010-05-11 13:18...
TestHelper.php
   <?php
// start output buffering
ob_start();

// set our app paths and environments
define('BASE_PATH', r...
ControllerTestCase.php
   <?php
require_once 'Zend/Application.php';
require_once 'Zend/Test/PHPUnit/ControllerTestCase.ph...
Directory Strategy
/application                 /application
    /configs                     /controllers
    /controller...
Testing Controllers
Homepage testing
    <?php
// file: tests/application/controllers/IndexControllerTest.php
require_once TEST_PATH . '/Contr...
Running the tests
testdox.html
Code coverage
Testing Forms
Simple comment form
   <?php
class Application_Form_Comment extends Zend_Form
{
    public function init()
    {
        $...
CommentController
<?php
class CommentController extends Zend_Controller_Action
{
    protected $_session;

    public func...
Comment processing
<?php
class CommentController extends Zend_Controller_Action
{
    …

    public function sendCommentAc...
Views
<!-- file: application/views/scripts/comment/index.phtml -->
<?php echo $this->form ?>

<!-- file: application/views...
The Form
Comment processed
And now… testing
Starting simple
<?php
// file: tests/application/controllers/IndexControllerTest.php
require_once TEST_PATH . '/Controller...
Can we submit our form ?
    public function testCanWeSubmitOurForm()
{
    $this->request->setMethod('post')
            ...
GET request = index ?
public function testSubmitFailsWhenNotPost()
{
    $this->request->setMethod('get');
    $this->disp...
All other cases ?
     /**
  * @dataProvider wrongDataProvider
  */
public function testSubmitFailsWithWrongData($fullName...
wrongDataProvider
public function wrongDataProvider()
{
    return array (
        array ('', '', ''),
        array ('~',...
Running the tests
Our testdox.html
Code Coverage
Unit Testing (models)
Guestbook Models
Testing models
• uses core PHPUnit_Framework_TestCase class
• tests your business logic !
• can run independent from other...
Model setUp/tearDown
   <?php
require_once 'PHPUnit/Framework/TestCase.php';
class Application_Model_GuestbookTest extends...
Simple tests
    public function testGuestBookIsEmptyAtConstruct()
{
    $this->assertType('Application_Model_GuestBook', ...
GuestbookEntry tests
     …
public function gbEntryProvider()
{
    return array (
        array (array (
            'ful...
Running the tests
Our textdox.html
Code Coverage
Database Testing
Database Testing
•- integration testing
     seeing records are getting updated
 -   data models behave as expected
Caveats
•- database should be reset in a “known state”
     no influence from other tests
•- system failures cause the test...
Converting modelTest
DatabaseTestCase.php
<?php
require_once 'Zend/Application.php';
require_once 'Zend/Test/PHPUnit/DatabaseTestCase.php';
req...
DatabaseTestCase.php (2)
    …
    public function appBootstrap()
    {
        $this->application->bootstrap();
    }
   ...
_files/initialDataSet.xml
<?xml version="1.0" encoding="UTF-8"?>
<dataset>
    <gbentry id="1"
             fullName="Test ...
Model => database
<?php
require_once 'PHPUnit/Framework/TestCase.php';
class Application_Model_GuestbookEntryTest extends ...
A simple DB test
    public function testDatabaseCanBeRead()
{
    $ds = new Zend_Test_PHPUnit_Db_DataSet_QueryDataSet(
  ...
location of datasets
<approot>/application
         /public
         /tests
            /_files
                 initialDa...
Running the tests
Our textdox.html
CodeCoverage
Changing records
public function testNewEntryPopulatesDatabase()
{
    $data = $this->gbEntryProvider();
    foreach ($dat...
Expected resultset
   <?xml version="1.0" encoding="UTF-8"?>
<dataset>
    <gbentry fullName="Test User" emailAddress="tes...
location of datasets
<approot>/application
         /public
         /tests
            /_files
                 initialDa...
Running the tests
The testdox.html
CodeCoverage
Testing strategies
Desire vs Reality
•- desire
     +70% code coverage
 -   test driven development
 -   clean separation of tests

•- realit...
Automation
•- using a CI system
   continuous running your tests
 - reports immediately when failure
 - provides extra inf...
Thank you


•   http://slideshare.net/DragonBe
•   http://twitter.com/DragonBe
•   http://facebook.com/DragonBe
•   http:/...
Unit testing after Zend Framework 1.8
Unit testing after Zend Framework 1.8
Unit testing after Zend Framework 1.8
Unit testing after Zend Framework 1.8
Unit testing after Zend Framework 1.8
Unit testing after Zend Framework 1.8
Unit testing after Zend Framework 1.8
Unit testing after Zend Framework 1.8
Upcoming SlideShare
Loading in...5
×

Unit testing after Zend Framework 1.8

48,946

Published on

Zend Framework 1.8 changed internally, making it easier to fully test your controllers, forms, models and datatabase integration with PHPUnit.

3 Comments
19 Likes
Statistics
Notes
No Downloads
Views
Total Views
48,946
On Slideshare
0
From Embeds
0
Number of Embeds
9
Actions
Shares
0
Downloads
462
Comments
3
Likes
19
Embeds 0
No embeds

No notes for slide

Transcript of "Unit testing after Zend Framework 1.8"

  1. 1. Unit Testing after ZF 1.8 Michelangelo van Dam PHPBenelux Meeting September 2010, Rijswijk (NL)
  2. 2. Michelangelo van Dam • Independent Consultant • Zend Certified Engineer (ZCE) - PHP 4 & PHP 5 - Zend Framework • Co-Founder of PHPBenelux • Shepherd of “elephpant” herds
  3. 3. This session What’s 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. 4. New to unit testing ?
  5. 5. phpunit.de http://www.phpunit.de
  6. 6. Matthew Weier O’Phinney http://www.slideshare.net/weierophinney/testing-zend-framework-applications
  7. 7. Zend Framework 1.8
  8. 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, …)
  9. 9. Types of tests
  10. 10. 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 bugfixing (is it really a bug ?) • TDD results in better code • higher confidence for developers and managers
  11. 11. 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, …)
  12. 12. Database Testing •- Tests the functionality of your database referred to as “integration testing” •- Checks if your data gets modified is that field updated ? - is that row added ? - is that row deleted ?
  13. 13. Application Testing
  14. 14. Setting things up
  15. 15. phpunit.xml <phpunit bootstrap="./TestHelper.php" colors="true"> <!-- Version: $Id: phpunit.xml 298 2010-05-11 13:18:02Z michelangelo $ --> <testsuite name="Zend Framework Unit Test Demo"> <directory>./</directory> </testsuite> <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>
  16. 16. TestHelper.php <?php // start output buffering ob_start(); // 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();
  17. 17. 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 } }
  18. 18. Directory Strategy /application /application /configs /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
  19. 19. Testing Controllers
  20. 20. 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); } }
  21. 21. Running the tests
  22. 22. testdox.html
  23. 23. Code coverage
  24. 24. Testing Forms
  25. 25. 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)); } }
  26. 26. 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; } }
  27. 27. 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; } }
  28. 28. 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>
  29. 29. The Form
  30. 30. Comment processed
  31. 31. And now… testing
  32. 32. 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);
  33. 33. 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'); }
  34. 34. GET request = index ? public function testSubmitFailsWhenNotPost() { $this->request->setMethod('get'); $this->dispatch('/comment/send-comment'); $this->assertResponseCode(302); $this->assertRedirectTo('/comment'); }
  35. 35. 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'); }
  36. 36. 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)), ); }
  37. 37. Running the tests
  38. 38. Our testdox.html
  39. 39. Code Coverage
  40. 40. Unit Testing (models)
  41. 41. Guestbook Models
  42. 42. 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
  43. 43. 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(); } … }
  44. 44. 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 testGuestbookAdsEntry() { $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)); }
  45. 45. 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()); } …
  46. 46. Running the tests
  47. 47. Our textdox.html
  48. 48. Code Coverage
  49. 49. Database Testing
  50. 50. Database Testing •- integration testing seeing records are getting updated - data models behave as expected
  51. 51. Caveats •- database should be reset in a “known state” no influence from other tests •- system failures cause the test to fail as well connection problems •- system sequence cannot be predicted auto increment fields are “unpredictable”
  52. 52. Converting modelTest
  53. 53. 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(); } …
  54. 54. 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'); } }
  55. 55. _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"/> </dataset>
  56. 56. 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 { … }
  57. 57. A simple DB test public function testDatabaseCanBeRead() { $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/readingDataFromSource.xml"), $ds ); }
  58. 58. location of datasets <approot>/application /public /tests /_files initialDataSet.xml readingDataFromSource.xml
  59. 59. Running the tests
  60. 60. Our textdox.html
  61. 61. CodeCoverage
  62. 62. 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 ); }
  63. 63. 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>
  64. 64. location of datasets <approot>/application /public /tests /_files initialDataSet.xml readingDataFromSource.xml addedTwoEntries.xml
  65. 65. Running the tests
  66. 66. The testdox.html
  67. 67. CodeCoverage
  68. 68. Testing strategies
  69. 69. 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
  70. 70. 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 ‣ …
  71. 71. Thank you • http://slideshare.net/DragonBe • http://twitter.com/DragonBe • http://facebook.com/DragonBe • http://joind.in/2044
  1. A particular slide catching your eye?

    Clipping is a handy way to collect important slides you want to go back to later.

×