Sean’s Obsessions

Sean Walberg’s blog

Unit Testing PHP Using Apache-Test

After being reminded that Debugging sucks, Testing rocks I thought I’d go back to some PHP stuff I was working on and develop some unit tests. I looked around at a few testing packages including PHPUnit2 as recommended by Jack Herrington in PHP Hacks, but the overhead and complexity of creating classes to encapsulate my test was too much. Plus, I wanted to be able to test on PHP 4 and 5. Finding examples was also difficult, everyone seemed to be showing off a class that simply adds two numbers which to me is fairly useless. I wanted a real world example of someone testing code as ugly as mine.

Trying to compare different methods led me to Power PHP Testing which is a good description of testing in PHP and the various methods, and also showed me about Apache-Test.

Apache-Test is a project by the Apache foundation that will create a local instance of a web server for hosting the tests, and also uses the “Test Anything Protocol” that I was familiar with from Perl. Each test spits out something like “OK” which makes collecting and generating a simple unit test easy. The documentation on Apache-Test was quite extensive, but still it was difficult to get started with a “Hello World!” type of test to make sure everything is running. To start off I installed Apache::Test from CPAN (perl -MCPAN -e ‘install Apache::Test’)

Assume the app is in /var/www/html, and we want to test get.php which gets a web page using cURL.


function get($url) {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$response = curl_exec($ch);
curl_close($ch);
return $response;
}

?>

The basic tests I’d like to run on this are to make sure get.php compiles, to get a simple web page, and then to introduce a few error conditions and see what happens. (Note the latter ones will probably fail because there is no error checking, defining the tests beforehand is an advantage then to ensure I don’t forget something)

To implement the testing environment:

mkdir -p t/response/TestGet
cat > Makefile.pl
# don't worry about this file - you should not
# need to edit it at all.

use 5.00501;

use ExtUtils::MakeMaker;

use lib qw(lib);

use Apache::TestMM qw(test clean);
use Apache::TestRunPHP ();

Apache::TestMM::filter_args();

Apache::TestRunPHP->generate_script();

WriteMakefile(
NAME => 'perl-php',
VERSION => 'test',
);
^D
perl Makefile.PL

Each series of tests is a directory in the t/response directory with a name starting with Test. I’ve called the tests relating to get.php “Get”, so all my tests for get.php go in t/response/TestGet.

Before getting into the tests, you have to understand the environment you’ll be testing in. The test script will be running on a web server on a non standard port (8529 by default) with a document root of t/htdocs. Each testing directory (ie TestGet) has an Alias directive so that a request to /TestGet/foo will be mapped to t/response/TestGet/foo.

So the first test to make sure get.php loads fine is


require "test-more.php";
require "../../../get.php";

plan(1);

ok(1);

?>

Note that I needed to require get.php out of a parent directory. There are ways to modify the config files to change the php include_path but this is easy enough now.

The test-more.php file comes with Apache-Test and implements some testing functions that help. I started off with plan(1) which tells the testing harness that 1 tests are expected. The ok() function is a test, if whatever is inside the parenthesis is true the tests pass.

[sean@bob html]$ make test
/usr/bin/perl -Iblib/arch -Iblib/lib \
t/TEST -clean
[warning] setting ulimit to allow core files
ulimit -c unlimited; /usr/bin/perl /var/www/html/t/TEST -clean
APACHE_TEST_GROUP= APACHE_TEST_HTTPD= APACHE_TEST_PORT= APACHE_TEST_USER= APACHE_TEST_APXS= \
/usr/bin/perl -Iblib/arch -Iblib/lib \
t/TEST -bugreport -verbose=0
[warning] setting ulimit to allow core files
ulimit -c unlimited; /usr/bin/perl /var/www/html/t/TEST -bugreport -verbose=0
/usr/sbin/httpd -d /var/www/html/t -f /var/www/html/t/conf/httpd.conf -D APACHE2 -D PERL_USEITHREADS
using Apache/2.2.3 (prefork MPM)

waiting 60 seconds for server to start: ..
waiting 60 seconds for server to start: ok (waited 1 secs)
server localhost:8529 started
t/get/0_test....ok
All tests successful.
Files=1, Tests=1, 2 wallclock secs ( 1.78 cusr + 0.32 csys = 2.10 CPU)
[warning] server localhost:8529 shutdown

I’ll create another test to get two pages, one that exists and one that doesn’t. Apache-Test places an index.html in the docroot for testing which I’ll use.


require "test-more.php";
require "../../../get.php";

plan(2);

$result = get("http://localhost:8529/index.html");
like($result, "/welcome to/i");

$result = get("http://localhost:8529/idontexist.html");
is($result, NULL);

?>

Here I used two more testing functions. like() accepts a regular expression that gets passed to preg_match. If the regexp matches the string in the first parameter, the test passes. is() compares two values. Here I’d expect that if the page wasn’t found I’d get a NULL.

Then I run it again

[sean@bob html]$ make test
/usr/bin/perl -Iblib/arch -Iblib/lib \
t/TEST -clean
[warning] setting ulimit to allow core files
ulimit -c unlimited; /usr/bin/perl /var/www/html/t/TEST -clean
APACHE_TEST_GROUP= APACHE_TEST_HTTPD= APACHE_TEST_PORT= APACHE_TEST_USER= APACHE_TEST_APXS= \
/usr/bin/perl -Iblib/arch -Iblib/lib \
t/TEST -bugreport -verbose=0
[warning] setting ulimit to allow core files
ulimit -c unlimited; /usr/bin/perl /var/www/html/t/TEST -bugreport -verbose=0
/usr/sbin/httpd -d /var/www/html/t -f /var/www/html/t/conf/httpd.conf -D APACHE2 -D PERL_USEITHREADS
using Apache/2.2.3 (prefork MPM)

waiting 60 seconds for server to start: ...
waiting 60 seconds for server to start: ok (waited 2 secs)
server localhost:8529 started
t/get/0_test....ok
t/get/1_get.....FAILED test 2
Failed 1/2 tests, 50.00% okay
Failed Test Stat Wstat Total Fail List of Failed
-------------------------------------------------------------------------------
t/get/1_get.t 2 1 2
Failed 1/2 test scripts. 1/3 subtests failed.
Files=2, Tests=3, 5 wallclock secs ( 3.75 cusr + 0.64 csys = 4.39 CPU)
Failed 1/2 test programs. 1/3 subtests failed.
[warning] server localhost:8529 shutdown
[ error] error running tests (please examine t/logs/error_log)
make: *** [run_tests] Error 1

It looks like the second test failed… I
[sean@bob html]$ make test
/usr/bin/perl -Iblib/arch -Iblib/lib \
t/TEST -clean
[warning] setting ulimit to allow core files
ulimit -c unlimited; /usr/bin/perl /var/www/html/t/TEST -clean
APACHE_TEST_GROUP= APACHE_TEST_HTTPD= APACHE_TEST_PORT= APACHE_TEST_USER= APACHE_TEST_APXS= \
/usr/bin/perl -Iblib/arch -Iblib/lib \
t/TEST -bugreport -verbose=0
[warning] setting ulimit to allow core files
ulimit -c unlimited; /usr/bin/perl /var/www/html/t/TEST -bugreport -verbose=0
/usr/sbin/httpd -d /var/www/html/t -f /var/www/html/t/conf/httpd.conf -D APACHE2 -D PERL_USEITHREADS
using Apache/2.2.3 (prefork MPM)

waiting 60 seconds for server to start: …
waiting 60 seconds for server to start: ok (waited 2 secs)
server localhost:8529 started
t/get/0_test….ok
t/get/1_get…..FAILED test 2
Failed 1/2 tests, 50.00% okay
Failed Test Stat Wstat Total Fail List of Failed
——————————————————————————-
t/get/1_get.t 2 1 2
Failed 1/2 test scripts. 1/3 subtests failed.
Files=2, Tests=3, 5 wallclock secs ( 3.75 cusr + 0.64 csys = 4.39 CPU)
Failed 1/2 test programs. 1/3 subtests failed.
[warning] server localhost:8529 shutdown
[ error] error running tests (please examine t/logs/error_log)
make: *** [run_tests] Error 1

The final test of a missing page failed. I can enable more debug output with TEST_VERBOSE=1:

make test
...
t/get/1_get.....1..2
ok 1
not ok 2
# Failed test (t/response/TestGet/1_get.php at line 12)
# got: '

404 Not Found

Not Found

The requested URL /idontexist.html was not found on this server.



'
# expected: ''

# Looks like you failed 1 tests of 2.
FAILED test 2
...

So, the script returns the contents of the 404 page rather than the NULL. After fixing that up my tests pass.

Comments

I’m trying something new here. Talk to me on Twitter with the button above, please.