Sign In/My Account | View Cart  
advertisement


Listen Print

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>&quot;This is a test&quot;</b></i>
  

where tags are shortened and whitespace removed but the &quot; 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>&quot;This is a test&quot;</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>&quot;This is a test&quot;</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>&quot;This is a test&quot;</b></i>
# received: <i><b>&quot;This is a test&quot;</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>&quot;This is a test&quot;</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.

Pages: 1, 2, 3

Next Pagearrow