PHP + Unit Testing = Pain in the rear!
I have a fairly large library of PHP code that I maintain. It’s been one of my goals over the last year to implement unit testing for as much of this code as I can. The process has gone fairly well, but one BIG issue always seems to come up: when I attempt to group my tests into suites, class conflicts occur.
This is due to the fact that I have stubbed out many of the classes that are dependencies. These classes, obviously, have the exact same name as the classes they are replacing. If you include a test for object X and then include a test for object Y which requires X (which has been thoughtfully stubbed out), you get this fine error:
Fatal error: Cannot redeclare class X in Y on line N
I know there are various ways around this, many of which require a fair amount of refactoring. I just don’t have the time or energy to undertake that task right now. BUT I still want an AllTests suite that will allow me to run all my unit tests for my project at one time! Manually running hundreds of test is no good. Manually running dozens of small suites is no good. Here is my work around.
Goal
Go from hand executing a ton of tests/suites to executing all of them in a single call. It would also be nice if we end up with some nice output from the entire things. Say, something like this:
1. We need an install of PHPUnit
Install PHPUnit from PEAR. You can find the instructions here: http://www.phpunit.de/manual/current/en/installation.html
I’m currently using v3.5. It won’t work with the PHPUnit that comes with Zend Studio. You also have the added bonus of using the most recent version, which can be very helpful, indeed.
2. Setup you suites as XML files
Let’s assume the following directory structure for this example:
/tests/unit/library/project/
Our next step is to use the XML configuration system for setting up PHPUnit test suites. I typically put my xml files in the same directory as the tests. Here is an example of one of my files:
library/Project/Class1Test.php
library/Project/Class2Test.php
...
I use /tests/unit as my root directory for all things unit test. The bootstrap.php is located here and all my tests are referenced from that directory. You will notice that the bootstrap is referenced from the location of the AllTest.xml file.
3. The bootstrap.php file
The bootstrap.php file is used to setup anything that is needed commonly for your testing environment that can’t be done from your tests. This includes things like PHP INI directives and environmental variables.
<?php
set_include_path(implode(PATH_SEPARATOR, array(realpath(dirname(__FILE__)), get_include_path(),)));
$newPath = "../../";
set_include_path(implode(PATH_SEPARATOR, array(realpath(dirname(__FILE__) ."/". $newPath), get_include_path(),)));
My file sets the unit test path and my project root path in the php include_path. Place this file in /tests/unit.
4. AllTest.bat file
Yes. I’m in a windows environment. WISP to be specific, and all the joy and happy butterfly flowers that it brings. That said, batch files seemed to be a decent way to go to accomplish what I needed. PowerShell might have worked too, but I have no experience with it. So enter batch hell.
Rather than write out the entire file here, I’ll just summarize the basic functionality:
- Set environmental variables: working directory, timestamp, program root directory, config file location
- Read in the config file (unit.ini)
- Scan all the directories in the unit test folders looking for files named AllTest*.xml
- Execute phpunit on each XML file, using the –log-junit switch to generate an xml log file for each test.
- Execute my merge.php script to take the individual XML log files and merge them into a single XML file. The script also executes an XSL transformation on the log file to generate an HTML output file.
- Open the HTML output file in firefox for viewing.
You will notice several files that I listed in there. Here’s what they are:
- unit.ini: a basic configuration file. This was an attempt to handle as much configuration as possible outside the batch file. Moderately successful.
- merge.php: a php script that reads in all the phpunit generated log files, merges them together into a single XML log file and executes and XSL transformation on the final log file.
- plain.xsl: a modified version of the JUnit stylesheet. Used to transform the log.xml file into a log.html file for easy viewing
- log-x.xml: the various xml file output by PHPUnit. One for each AllTest*.xml file that it encounters
- log.xml: the merged version of the individual xml files.
- log.html: an easy to view version summary of the phpunit output
You can take a look at each of these files. Once everything is said and done, I end up with a very useful summary of my phpunit tests. It’s actually more useful than the output that is shown in Zend Studio (v8 beta2).
It might also be a good idea to create a customized version of your PHP.ini file that has at least the php_xsl extension turned on as well as any other settings required for your environment.
Everything is pretty rough, but it works well enough. The same technique could easily be ported over to a shell script. It’s now saving me a lot of time and helps me quickly validate my entire library in a single execution.
Get the project files here:
Hi Seth,
I had no idea you were a PHP pro… me too!
Sounds like you might need 5+ with name spaces. Would that actually work given PHPUnit? Hmmm. Well at least you have good code coverage mine is completely jacked. Thanks for sharing.
Gary Malcolm
I’m doing everything under php 5.3. I’m currently setting up continuous integration using Hudson. I might be able to work around my problem with a automated builds using Apache Ant. It gives mean lot of added flexibility beyond what phpunit suites offer.