Sunday, November 01, 2009

Unit testing with Zend Framework 1.8+

I recently gave a presentation at ZendCon 2009 Uncon about "php unit with Zend Framework", which many people liked a lot (see comments on http://joind.in/638). But since then I got a lot of questions how to set up a testing environment for Zend Framework applications that uses version Zend Framework 1.8 or newer.

We start off by setting our environment best fitted for our unit testing. I use a virtual linux system for this, using VMWare, but with some extra background research these global settings can be applied for your own (test) environment as well.

I'm not going to explain how to install PHP here, but I can tell you I'm using PHP 5.3.0 (build from source). With this build, I've installed PHPUnit 3.4.2 and Xdebug 2.0.5. The example application was made using Zend Framework 1.9.5.

First we're creating a new Zend Framework project using Zend_Tool on command line, by issuing the command zf create project zfunit in the workspace directory I use to share my VMWare instance with my workstation.

Ok, we now have a default Zend Framework application running.




We're not going to build a complete application here, but just showing how you can start unit testing your Zend Framework project, so you can implement unit tests on your own applications.

First we need to modify the phpunit.xml file so we can use it as our unit testing configuration.


<phpunit bootstrap="./TestHelper.php" colors="true">
    <testsuite name="Zend Framework Unit Testing">
        <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>


As you see in the root node of our phpunit.xml file, we include the TestHelper script to aid us setting up the right testing environment. Of course, you can modify this file to fit your own application setup. This is my TestHelper script to set up my test environment, include paths and setting timezones.



// start output buffering
ob_start();

// set our app paths and environments
define('BASE_PATH', realpath(dirname(__FILE__) . '/../'));
define('APPLICATION_PATH', BASE_PATH . '/application');
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 'ControllerTestCase.php';



We create a custom ControllerTestCase that we can extend in all our controller test cases. This proves to be the most convenient way to handle the setup and teardown of the Zend Framework architecture.



require_once 'Zend/Application.php';
require_once 'Zend/Test/PHPUnit/ControllerTestCase.php';

abstract class ControllerTestCase extends Zend_Test_PHPUnit_ControllerTestCase
{
    public $application;

    public function setUp()
    {
        $this->application = new Zend_Application(
            APPLICATION_ENV,
            APPLICATION_PATH . '/configs/application.ini'
        );

        $this->bootstrap = array($this, 'appBootstrap');
        parent::setUp();
    }

    public function appBootstrap()
    {
        $this->application->bootstrap();
    }
}



And finally we create our first test on the default IndexController.



require_once realpath(dirname(__FILE__) . '/../../ControllerTestCase.php');

class IndexControllerTest extends ControllerTestCase
{
    public function testCallingRootTriggersIndex()
    {
        $this->dispatch('/');
        $this->assertController('index');
        $this->assertAction('index');
    }
  
    public function testCallingBogusTriggersError()
    {
        $this->dispatch('/bogus');
        $this->assertController('error');
        $this->assertAction('error');
        $this->assertResponseCode(404);
    }
}



Running phpunit from commandline will tell you that your assertions succeeded and all is a go.



All green, assertions succeeded and report is generated.

I've setup my test environment that directory log/report is aliased by /report, so I can view the code coverage reports (requires XDebug).
The following code coverage report is the result.


As you can see, with just a minimal effort you've set up your Zend Framework application to be unit tested. So now you don't have any more excuses to leave your applications untested.

Since I don't have all the knowledge, I would like to thank Matthew Weier O' Phinney and Mathias Geat for sharing their knowledge on the subject. Thanks guys !

13 comments:

Lance said...

I left ZendCon knowing what unit testing was, and why I needed to do it, but didn't have any idea how to start... thanks for the "foot in the door" tutorial.

Yaroslav Vorozhko said...

Thanks, very nice tutorial.

mweierophinney said...

BTW, as of 1.9.5, you can now assign a Zend_Application instance to $this->bootstrap; Zend_Test_PHPUnit_ControllerTestCase will then bootstrap() it during setUp().

Giorgio said...

Remember that this is how to test your controllers; to test other classes there is no need for the Zend_Test component, plain old phpunit will do a perfect job.

DragonBe said...

@Lance,

Glad to help you out here, I'll probably put some more examples on my blog, depending on the amount of questions I get.

@mweierophinney,

I did not know that, and you haven't blogged about it yet... but I'll look in to it and will post a follow up once I have figured it out.

Tnx for the hint !

@Giorgio,

Yes, this is solely for testing your controllers, actions and interactions with data sources (internal and external).

But I have to admit, having this combined with my own library components, I have one testsuite running all tests, independent from their purpose.

Anonymous said...

It looks like the xml for phpunit.xml is a bit messed up. No opening < for most of it, maybe other stuff?

Great post, though. Very helpful!

Greg

Anonymous said...

More on missing code: It looks like the <'s were escaped.

Thanks again,
Greg

Federico said...

One thing I found out a couple of months ago: If you have a build server and you are running Hudson, you can generate the code covergae report inside your workspace, bookmark any html page and share it with other members of your team.

Anonymous said...

What about Zend Debug? Can you get this to work with it instead of XDebug?

misbehavens said...

Thanks for your post. Can you please talk about testing models in a 1.8+ application? Thank you! Also, you still have errors in your XML code sample.

DragonBe said...

@misbehavens: I know, apparently there are a couple of escaping issues here. But any given XML editor will show you immediately what's wrong so you can fix it.

This example shows more the general idea behind unit testing instead being a simple copy/paste example.

I'm sorry for the misunderstanding and I'm looking into the whole escaping issue (already pointed out by @ananonymous #1 and @anonymous #2)

@anynymous #3: XDebug provides extra functionality used by PHPUnit to generate those coverage reports. I'm sure Zend Debug provides similar functionality, but I'm not sure how to implement it along with PHPUnit.

DragonBe said...

@Federico: do you have published an article of that setup ? I'm really interested in seeing this kind of setup.

shubhangi said...

Thanks for sharing unit testing so deeply.Can we aplly it to Module testing as well?

Sweeptakes

Post a Comment