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.