Testing mod_perl 2.0
by Geoffrey Young
|
Pages: 1, 2, 3
Writing the Tests
The Apache configuration we have created thus far
provides a way to test
Apache::Clean through
/level/index.html. The result of this
request should be that the default
Apache content handler serves up index.html,
applying our PerlOutputFilterHandler to
the file before it is sent over the wire. Given the
configured PerlSetVar CleanLevel 2 we would expect
the end results of the request to be
<i><b>"This is a test"</b></i>
where tags are shortened and whitespace removed but the
" entity is left untouched. Well,
maybe this is not what you would have expected, but
cracking open the code for HTML::Clean
reveals that level(2) includes the
whitespace and shortertags
options, but not the entities option. This
brings us to the larger issue of test design and the
possibility that flawed expectations can mask true bugs -
when a test fails, is the bug in the test or in the code? -
but that is a discussion for another time.
Given our configuration and expected results, we can craft
a test that requests /level/index.html,
isolates the content from the server response, then tests the content
against our expectations. The file t/01level.t
shown here does exactly that.
use strict;
use warnings FATAL => 'all';
use Apache::Test qw(plan ok have_lwp);
use Apache::TestRequest qw(GET);
use Apache::TestUtil qw(t_cmp);
plan tests => 1, have_lwp;
my $response = GET '/level/index.html';
chomp(my $content = $response->content);
ok ($content eq q!<i><b>"This is a test"</b></i>!);
t/01level.t illustrates a few of the things
that will be common to most of the tests you will write.
First, we do some bookkeeping and plan the number of
tests that will be attempted using the plan()
function from Apache::Test - in our case just one.
The final, optional argument to plan()
uses the have_lwp() function to check
for the availability of the modules from
the libwww-perl distribution. If
have_lwp() returns true, then we know we can
take advantage of the LWP shortcuts Apache::TestRequest
provides. If
have_lwp() returns false, then no tests are
planned and the entire test is skipped at runtime.
After planning our test, we use the shortcut function
GET() from Apache::TestRequest
to issue a request to /level/index.html.
GET() returns an HTTP::Response
object, so if you are familiar with the LWP
suite of modules you should feel right at home with
what follows. Using the object in $response we
isolate the server response using the content()
method and compare it against our expected string. The
comparison uses a call to ok(), which will
report success if the two strings are equivalent.
Keep in mind that
even though this example explicitly imported
the plan(), ok(),
have_lwp(), and GET()
functions into our test script, that was just to
illustrate the origins of the different parts of the
test - each of these functions, along with just about all
the others you may want, are exported by default. So,
the typical test script will usually just call
use Apache::Test;
use Apache::TestRequest;
and go from there.
That is all there is to writing the test. In its
simplest form, using Apache-Test involves
pretty much the same steps as when
writing tests using other Perl testing tools: plan()
the number of tests in the script, do some stuff,
and call ok() for each test you plan().
Apache-Test and its utility classes merely offer
shortcuts that make writing tests against a
running Apache server idiomatic.
Running the Tests
With all the preparation behind us - generating and
customizing the Makefile.PL, configuring
Apache with extra.conf.in, writing
index.html and 01level.t -
we have all the pieces in place and can (finally) run our test.
There are a few different ways we can run the tests in a distribution, but all require that we go through the standard build steps first.
$ perl Makefile.PL -apxs /usr/local/apache2/bin/apxs
Checking if your kit is complete ...
Looks good
Writing Makefile for Apache::Clean
$ make
cp Clean.pm blib/lib/Apache2/Apache/Clean.pm
Manifying blib/man3/Apache::Clean.3
Makefile.PL starts the process by generating the
t/TEST script via the call to
Apache::TestRunPerl->generate_script().
The additional argument we pass, -apxs, is
trapped by Apache::TestMM::filter_args()
and is used to specify the Apache installation we want to
test our code against. Here, I use -apxs to
specify the location of the apxs binary
in my local Apache DSO installation - for
static builds you will want to use -httpd to
point to the httpd binary instead. By the
time Makefile.PL exits,
we have our test harness and know where our server lives.
Running make creates our build directory,
blib/, and installs Clean.pm
locally so we can use it in our tests. Note that
ModPerl::MM installed Clean.pm
relative to Apache2, magically following
the path of my current mod_perl 2.0 installation.
At this point, we can run our tests. Issuing
make test will run all the tests in
t/, as you might expect. However,
we can run our tests individually as well, which is particularly
useful when debugging. To run a specific test we call
t/TEST directly and give it the name of the
test we are interested in.
$ t/TEST t/01level.t
*** setting ulimit to allow core files
ulimit -c unlimited; t/TEST 't/01level.t'
/usr/local/apache2/bin/httpd -d /src/perl.com/Apache-Clean-2.0/t
-f /src/perl.com/Apache-Clean-2.0/t/conf/httpd.conf
-DAPACHE2 -DPERL_USEITHREADS
using Apache/2.1.0-dev (prefork MPM)
waiting for server to start: ..
waiting for server to start: ok (waited 1 secs)
server localhost:8529 started
01level....ok
All tests successful.
Files=1, Tests=1, 4 wallclock secs ( 3.15 cusr + 0.13 csys = 3.28 CPU)
*** server localhost:8529 shutdown
As you can see, the server was started, our test was run,
the server was shutdown, and a report was generated - all with
what is really minimal work on our part. Major kudos to the
Apache-Test developers for making the
development of live tests as easy as they are.
Beyond the Basics
What we have talked about so
far is just the basics, and the framework is full of a number
of different options designed to make writing and debugging tests
easier. One of these is the Apache::TestUtil package,
which provides a number of utility functions you can use in your
tests. Probably the most helpful of these is t_cmp(),
a simple equality testing function that also provides
additional information when you run tests in verbose mode.
For instance, after adding use Apache::TestUtil;
to our 01level.t test, we can alter the call
to ok() to look like
ok t_cmp(q!<i><b>"This is a test"</b></i>!, $content);
and the result would include expected and received notices (in addition to standard verbose output)
$ t/TEST t/01level.t -v
[lines snipped]
01level....1..1
# Running under perl version 5.009 for linux
# Current time local: Mon May 5 11:04:09 2003
# Current time GMT: Mon May 5 15:04:09 2003
# Using Test.pm version 1.24
# expected: <i><b>"This is a test"</b></i>
# received: <i><b>"This is a test"</b></i>
ok 1
ok
All tests successful.
which is particularly helpful when debugging problems
reported by end users of your code. See the
Apache::TestUtil manpage for a long
list of helper functions, as well as the README
in the Apache-Test distribution for
additional command line options over and above
-v.
Of course, 01level.t only tests one
aspect of our Clean.pm output filter,
and there is much more functionality in the filter that
we might want verify. So, let's take a quick look at some
of the other tests that accompany the Apache::Clean
distribution.
One of the features of Apache::Clean is that
it automatically declines processing non-HTML documents.
The logic for this was defined in just a few lines at
the start of our filter.
# we only process HTML documents
unless ($r->content_type =~ m!text/html!i) {
$log->info('skipping request to ', $r->uri, ' (not an HTML document)');
return Apache::DECLINED;
}
A good test for this code would be verifying that content from a plain-text
document does indeed pass through our filter unaltered, even
if it has HTML tags that HTML::Clean would
ordinary manipulate.
Our test suite includes a file t/htdocs/index.txt
whose content is identical to the index.html file
we created earlier. Remembering that we already have an
Apache configuration for /level that inserts
our filter into the request cycle, we can use a request for
/level/index.txt to test the decline logic.
use Apache::Test;
use Apache::TestRequest;
plan tests => 1, have_lwp;
my $response = GET '/level/index.txt';
chomp(my $content = $response->content);
ok ($content eq q!<i><strong>"This is a test"</strong></i>!);
It may be obvious, but if you think about what we are really testing here it is not that the content is unaltered - that is just what we use to measure the success of our test. The real test is against the criterion that determines whether the filter acts on the content. If we wanted to be really thorough, then we could add
AddDefaultCharset On
to our extra.conf.in to test the Content-Type
logic against headers that look like text/html; charset=iso-8859-1
instead of just text/html. I actually have had
more than one person comment that using a regular expression for testing the
Content-Type is excessive - adding the
AddDefaultCharset On directive shows that
the regex logic can handle more runtime environments than a simple
$r->content_type eq 'text/html' check. Oh,
the bugs you will find, fix, and defend when
you start writing tests.

