Okay, I'll admit it, I'm a bit of a testing freak. I like to test my code extensively to make sure it works properly before I ship it.
Last year I talked about using
The trouble simply using Test::More is that often this debugging information isn't enough, or it's too much. Checking individual strings are equal or that simple tests pass doesn't give you enough information, or it prints so much information to your screen that it's hard to see what's going on. What you need to do is code your own little routines that check the data themselves and print out more useful debugging information for the project you're working on.
This is where Test::Builder comes in. Test::Builder is the underlying library that Test::More uses to produce it's output, and it's cunningly designed so that you can write your own test suites with it that always print out the right kind of debugging information. The best thing about these modules is that they're designed to play nice with other Test::Builder based modules. You can use as many of these modules together as you want and they're work together to ensure that things like the numbers that your tests print don't get screwed up.
Okay, let's build our own example testing module. In this example we're going to test something pretty useless - if a string equals "Buffy" or not. In a real world example the test would do something much more complicated, but for illustrative purposes, we'll keep it simple.
We start the testing module just like any other - by declaring a
package, turning on strict, and declaring the package variables we're
going to use. Note that testing modules traditionally work on very old
perls, so it's common for them to use things like use vars
instead
of using the our
keyword for globals to ensure backwards
comparability.
package Acme::Test::Buffy;
use strict; use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
$VERSION = 0.01;
Now we create a Test::Builder object that we can use in each of the
methods in our class. Test::Builder objects are what are known as
'singletons'. This means that there's only one existence ever - each
call to new
simply returns the same object each and every time.
This is how Test::Builder can keep track of numbers for you and
emit the right numbers for each test - every module has it's own copy
of the same Test::Builder object that it uses for all of it's output.
use Test::Builder; my $Tester = Test::Builder->new();
Since we want to allow people to run the tests with the minimum of
fuss we're going to export our function into the caller's namespace.
That means that when someone does a use Acme::Test::Buffy
in their
code that they'll get the ability to is_buffy
in script as if they'd
written it in place - just like Test::More does with is
, ok
,
etc.
use Exporter; # load the class @ISA = qw(Exporter); # set it as the base class @EXPORT = qw(is_buffy); # want to export 'is_buffy' @EXPORT_OK = qw(); # no other optional functions %EXPORT_TAGS = qw(); # no groups of functions
Now all that's left to do is write our is_buffy
function itself.
The function is designed to be called like so:
is_buffy($foo, "Is foo buffy?");
So without further ado:
sub is_buffy($;$) { # get the args my ($maybe_buffy, $text) = @_;
# set a default test name $text ||= "is buffy";
# do the test if ($maybe_buffy eq "Buffy") { # print okay with the right text $Tester->ok(1,$text);
return 1; # and return true } else { # print not okay with the right text $Tester->ok(0,$text);
# print why it failed $Tester->diag("Expected 'Buffy' but got '$maybe_buffy' instead\n");
return 0; # and return false } }
Okay, quite a lot to explain here. First I should point out the
function prototype (the ($;$)
). This tells Perl how people can
call our module - essentially it allows people to be a lot less fussy
about having to write brackets. In fact it means that now people can
simply write
is_buffy $foo;
And it'll still work.
The real meat of the testing function is the $maybe_buffy eq "Buffy"
section. This is a simple test, but this could be as complicated as
you want. It decides what functions we should call on our Test::Builder
object to produce the output. We either call ok(1,$text)
saying
that everything is okay and we should print out ok 1 - text
, or we
call ok(0,$text)
to say that everything isn't okay, and that we
should print out not ok - 1 text
instead, and go onto use the
diag function to print out the exact reason the test failed.
Though it might be tempting to try and print out some of the ok
or
diagnostic messages ourselves we really shouldn't - as by printing
everything out via Test::Builder we ensure the mutual cooperation
between all the testing modules, meaning we can do something like
use Test::More tests => 2; use Acme::Test::Buffy;
# test our re works my ($buffy, $angel) = split /:/, "Buffy:Angel";
is_buffy($buffy); # an Acme::Test::Buffy test is($angel, "Angel"); # a Test::More test
One of the great things about Test::Builder is that it always reports
the line number of the failing test. It is possible to confuse it
however, as all it really does is print out where the function that
called Test::Builder's ok
method was called from. So in this situation
# simply call the private method instead sub is_buffy { _isnt_robot_buffy(@_) }
# private method not exported from the package sub _isnt_robot_buffy { $Tester->ok(0); }
The diagnostic error for the failing test will be reported inside the
is_buffy
function as that's the function that called ok
, not
from the code that called the is_buffy
test where it was meant to
be. Nasty!
To solve this problem we simply increase the $Test::Builder::Level
variable to indicate we'd like the Test::Builder to look one level
higher when reporting errors for the duration of _isnt_robot_buffy
.
Luckily for us, Perl has a keyword to do just that. local
can give
a variable a new value that will remain in effect until the end of that
block, i.e. till the end of the subroutine, including in all the calls
to other routines such as to Test::Builder's ok
.
# simply call the private method instead sub is_buffy { isnt_robot_buffy(@_) }
# private method not exported from the package sub _isnt_robot_buffy { # increase the level local $Test::Builder::Level = $Test::Builder::Level + 1;
$Tester->ok(0); }