Skip to content

Avoiding common pitfalls with Zend_Test

Sometimes I think I'm particularly stupid when it comes to learning new things. Well, that, or maybe I'm just the only one complaining enough. ;-)

I feel like I've wasted a great deal of time last week with basics that were nowhere to be found or required debugging to no end. The following is the outcome, a rather random list of things to watch out for when you're starting on Zend_Test.

A general understanding of testing and PHPUnit is more than helpful.

How do you debug?

Did your test not redirect, or did your query assertions go wrong?

Generally, there are two ways:

<?php
// (...)
function testIfTheSunCameUp()
{
    var_dump($this->response, $this->request);
}

Not so pretty, right?

In general I don't understand why it will say, "failed asserting status code 404", and will not tell you what it got instead. It offers you a backtrace so you can go into the class and add var_dump(), but that's hardly useful.

One of the things I love about PHPT is that a failed test case is so descriptive. There's really no debugging a failed test because the process of debugging generally involves gathering information where PHPT hands it over to you right away. But maybe that's a PHPUnit thing.

And of course:

phpunit --verbose AllTests.php

My grudge here is that I'm a total PHPUnit newbie. Before Zend_Test I avoided PHPUnit where I could because I felt that it is a beast — one that's especially hard to tame.

Update: Someone else noticed too, please vote on ZF-6013.

Control your environment

We offer a REST-API to partners and that API is basically MVC and lots of context switching depending on GET, POST, PUT, HEAD, DELETE and a couple parameters. I like to think of it as true REST. ;-)

Since the unit tests are run on the cli, not even GET is set. So make sure to add this to your test:

<?php
// (...)
$this->request->setMethod('GET');

Keep in mind that $_SERVER is not set and if you really happen to rely on anything from that ($this->getServer()), you need to explicitly set it or figure out another smart way to deal with it. In my case, I'm overriding some of these $_SERVER variables in my bootstrap with a config setting when I'm using the "testing" environment.

Update: I've opend ZF-6162, please vote.

The basic AllTests.php

AllTests.php is used to tell phpunit what to do, a simple setup would look like this:

tests/AllTests.php

tests/controllers/AllTests.php

tests/controllers/IndexControllerTest.php

tests/controllers/AllTests.php:

<?php
require_once dirname(__FILE__) . '/IndexControllerTest.php';

class ControllersAllTests
{
    public static function main()
    {
        PHPUnit_TextUI_TestRunner::run(self::suite());
    }

    public static function suite()
    {
        $suite = new PHPUnit_Framework_TestSuite('Controllers');
        $suite->addSuiteCase('IndexControllerTestCase');
        return $suite;
    }
}

tests/AllTests.php:

<?php
(...)
class AllTests
{
    public static function suite()
    {
        $suite = new PHPUnit_Framework_TestSuite('AllTests');
        $suite->addSuite(ControllersAllTests::suite());
        return $suite;
    }
}

Zend_Session_Namespace

Avoid locking session namespaces, they will stab you in the eye. See ZF-6072.

Zend_Dom_Query

I'd suggest you remove all @-operators from Zend_Dom_Query since it doesn't really tell you what is wrong with the response otherwise. I've opened ZF-6142 so this gets fixed.

Slightly related to Zend_Dom_Query is a bug I noticed when you're response is empty. It's a rather trivial issue where null is not the same as an empty string. I've opened ZF-6143 and included a patch.

Bootstrapping the correct way

The most simple way to bootstrap is to create a small class to initialize your application. The class takes an environment parameter to __construct() which allows you to load a different config section or other nifty stuff. I called mine Lagged_App() and the code came originally from Andries Seuten's example ZF application but it evolved over the past years.

I wouldn't recommend his tutorial today (because things changed since 2007), but at the time it was pretty easy and straight forward. Today's bar is the quickstart section in the official manual. But since the quickstart suggests includes, I spent some time cleaning up my by going through said quickstart guide and I posted a quick and dirty example (which I labeled Lagged_Application)) in my subversion repository on Google Code.

A test case is a test case

I'm using the following "base" test case for all my controller tests:

<?php
class Lagged_PHPUnit_ControllerTestCase
    extends Zend_Test_PHPUnit_ControllerTestCase
{
    public function setUp()
    {
        Zend_Session::$_unitTestEnabled = true;

        $bootstrap       = new Bootstrap('testing');
        $this->bootstrap = array($bootstrap, 'start');
        parent::setUp();
    }
}

Clean up

One of the most fundamental things your mother tried to teach you.

This is in most of my tearDown()'s:

public function tearDown()
{
    $this->resetRequest()->resetResponse();
    $this->setQuery(array());
    $this->setPost(array());
}

Avoid direct PHP calls

I'd avoid the following:

  1. Skipping views with echo in the controller (bad, bad, bad ;-)).
  2. Use the Zend_Action_Helper redirector instead of header().
  3. Use Zend_Session etc. vs. session_*() (Don't forget Zend_Session::$_unitTestEnabled = true;).
  4. Don't dispatch always. ;-)

Drobo: USB device not recognized

I swear to god. Last year, I loved Drobos — but right now? Not so sure any more.

I recently had to return a 2nd generation Drobo unit. The 2nd generation Drobos have Firewire ports, while the 1st generation Drobos are sporting USB-only.

The issues with this 2nd generation unit started about 2 month ago when it stopped responding via USB. Because the EEEPC Box it is hooked up to doesn't have Firewire we couldn't verify if it was broken entirely. Also, it just stopped responding from one day to the other.

Of course I tried hooking it up to another PC and exchanged cables — to no avail.

The first symptoms included that the Drobo Dashboard doesn't respond anymore. Further more, when I copied or opened files on the Drobo I noticed read and write errors, access was slow and some files had disappeared all together. Once I rebooted the computer, the Dashboard opened but it wouldn't see the Drobo anymore, and the drives were not mounted either.

On the first incident the Drobo support was nice enough to ship us a new unit (in exchange for a credit card number). We returned the broken unit to them when we flipped the drives. About three weeks later (last Friday), the issues returned.

How do you recover from a broken USB port?

There are two procedures to follow, a short one and a slightly longer one.

  1. Remove all drives from the drobo.
  2. Pull the plug to power cycle the unit.

The Drobo will start up and if the Drobo is recognized by the Drobo Dashboard you got lucky. Insert all drives and let it rebuild. My volume is 4 TB total, across four disks; with 2TB usable. The rebuild took approximately 10 minutes. During the rebuild the Drobo was flashing the lights in orange and green.

In case the power cycling did not help, do a full reset; there's a hard reset switch in the back of the Drobo. And then try again.

In my case, no data was lost.

Conclusion?

All in all, I'm torn on the Drobo.

It is a great device on paper. I own a 1st generation Drobo myself and it has been running without issues for a long, long while (close to a year soon). While this other device was mostly idle, I'm stressing my own Drobo at times when I am synchronizing backups from the colo.

On the bright side, Drobo support is most definitely very helpful. They were very fast via email and had a fast turn around on the phone too. I just wish I didn't have to deal with them so often. So far the 2nd generation devices seem more prone to random errors.

Luckily no data was lost yet, but it also makes me think if I want to rely even in part on the Drobo for any backup what so ever. Of course one should not rely on a single point of failure anyway, but it's also rather annoying that a 500 EUR device fails so often.

We are TestFest'ing!

English: Berlin's PHP usergroup is taking part in PHP's TestFest 2009, on the 9th and 10th May, 2009! The location will be Boxhagener Str. 119, Berlin (Friedrichshain), if you want to attend, please RSVP on our wiki!

Starting it off, I'll give an intro to PHPT-style testing at the monthly meeting of the usergroup in May!

Deutsch: Die PHP Usergroup Berlin nimmt am PHP TestFest 2009 teil. Wir treffen uns dazu am 9. und 10. Mai in Berlin Friedrichshain. Wenn Ihr kommen wollt, tragt Euch bitte in unserem Wiki ein! Wenn jmd. einen Schlafplatz braucht, bitte vorher melden. :-)

Eine Einführung zu PHPT werde ich beim monatlichen Treffen der Usergroup im Mai geben — mehr dazu später!