Unit testing with PHPUnit

2011-01-19

This will likely be a multi-part series of posts, but I've been doing a lot of unit testing lately with PHPUnit and I feel I've learned several things worth sharing.

Debugging

Not everything you try in PHPUnit will work right away, and this is rarely PHPUnit's fault. When things to break, you have several options:

  1. Turn on display_errors and set error_reporting to a high value. This can be done in php.ini, the phpunit.xml file, as a command-line switch to the phpunit shell script, or directly in your bootstrap or test files using the ini_set and error_reporting functions. If your server configuration allows it, you can also set these values in .htaccess or .user.ini files.
  2. Turn off processIsolation. This is set as a command-line flag or in phpunit.xml. The extra buffering and wrapping this directive uses (while generally a good idea) can sometimes hide the errors that cause your test suite to silently fail.

Other things to remember

All code outside of test cases is global

This will mostly be require_once statements and the like, but if you end up setting variables or defining constants outside of your test, they will apply for the entire test suite, not just the individual test case file. This is not likely to cause big problems, but you might get "Constant already defined" E_NOTICE errors unless you're careful. Your code should limit the use of global constants anyway, but if you need to use them, set them in the phpunit.xml file, if possible.

Another place this might cause unexpected behavior is if you are testing an autoloader and classes are already defined from a previous test case (can cause false positives if a class was already loaded). This point applies even if you have preserveGlobalState set to false via annotation, phpunit.xml, or the setPreserveGlobalState() method.

The best way around this as far as autoloading goes is to remove the need for separate require_once calls by making your project compatible with the PHP Standards Group's recommendation for autoloader interoperability, which you can read here: https://groups.google.com/group/php-standards/web/psr-0-final-proposal?pli=1

The @covers annotation

This is much less likely to come up, but I added my @covers annotations via a find/replace for my "tests blah()" and didn't realize that @covers will cause PHPUnit to throw an error if the @covers annotation ends in parenthesis. Oops.

Very hard to test headers

PHPUnit is designed to run via the PHP CLI, and starts its own output before running even a user-defined bootstrap file, so it's difficult/impossible to test some situations where you need to check for user-defined headers. My example was a file downloader class that sent headers based on the requested MIME type, but there was no real way to verify them, even by checking headers_list(). The closest thing I could do to test the output was to collect the headers into an array, check its contents, and then send them and assume that PHP's header method worked properly.

Tags: php, phpunit

Comments