10 November 2012

Getting Testy

Been a while 

I'd apologise about the long interval between this post and the last, but posts apologising about lack of posts gets a bit tiresome on a blog. I've continued to work on programming bit by bit, and it's been code that is necessary but just isn't terribly interesting to talk about.

However, today I decided to bite the bullet and add some testing code to my library. I think Michael Schwern sums it up nicely on the Test::Tutorial perldoc page:-
AHHHHHHH!!!! NOT TESTING! Anything but testing! Beat me, whip me, send me to Detroit, but don't make me write tests! 
Suffice to say, Test::Simple goes out of its way to make things easier on the programmer, to encourage them to actually write tests. So I've been learning how it's done, and I'll share the basics with you.

Conventions

Apparently the general convention for perl testing modules is to make a tests/ directory, wherein your test code lives with a '.t' suffix. The 'ok()' sub from the Test::Simple package does most of the work for you. The most basic test would look something like this:- 
#!/usr/bin/perl

use warnings;
use strict;
use Test::Simple tests => 1;

ok( 1 + 1 == 2, "basic addition" );
Running the script will then tell you if any of the conditions in the ok() calls returned false. The string parameter to ok() is just a convenient description to use to help find out what went wrong.

A more practical example

Checking to see if basic mathematics has failed us is all well and good, but what if we want to test our important library code? That's a little more complicated for me, since I'm not testing a system library so it's not in @INC, perl's library search path. But wouldn't you know it, perl has a module for just such an eventuality: FindBin.
#!/usr/bin/perl

use warnings;
use strict;
use utf8;
use feature 'unicode_strings';

use FindBin qw($RealBin);
use lib "$RealBin/../../";

use Test::Simple tests => 1;


# As Hierarchical is a Role, we need to mix it into a class before using it.
{
        package HierTest;
        use Moose;
        with 'Decurse::Hierarchical';
}

my $h1 = HierTest->new();

ok( $h1->isa('Decurse::Hierarchical'), "new()" );
The good news is, this no longer complained about being unable to find my module. The bad news was, the test was failing - I had made an error which in hindsight was pretty obvious, but let's walk through it together:-
$ perl tests/Hierarchical.t 
1..1
not ok 1 - new()
#   Failed test 'new()'
#   at tests/Hierarchical.t line 27.
# Looks like you failed 1 test of 1.
So, Test::Simple can tell us what test failed, but it doesn't necessarily know why. What we can do here is drop in Test::Simple's more advanced sibling, Test::More. It has the same 'ok()' sub with a few nice extra variations. 'is()' is used when your test basically boils down to a comparison, and 'isa_ok()' can do the type check for us. Omitting the preamble, my test now looked like this:-
use Test::More tests => 1;


# As Hierarchical is a Role, we need to mix it into a class before using it.
{
        package HierTest;
        use Moose;
        with 'Decurse::Hierarchical';
}

my $h1 = HierTest->new();

isa_ok( $h1, 'Decurse::Hierarchical', "new()" );
Running it reveals the mistake I made when writing the test. Hey, nobody's perfect!
james@qitest4: ~/source/perl/aitl/libs/Decurse
$ perl tests/Hierarchical.t 
1..1
not ok 1 - new() isa Decurse::Hierarchical
#   Failed test 'new() isa Decurse::Hierarchical'
#   at tests/Hierarchical.t line 27.
#     new() isn't a 'Decurse::Hierarchical' it's a 'HierTest'
# Looks like you failed 1 test of 1.
I had mistakenly assumed the 'isa' relationship would include what roles a class consumed, but it only tests the actual class name itself, which in my test is 'HierTest'. I needed a completely different way to check the object was properly consuming that role. Naturally, Moose has a way to do that, and I only needed to change the test to read:-
ok( $h1->meta->does_role('Decurse::Hierarchical'), "new() does Hierarchical" );
After fixing that, my test suite (of one whole test) now works!
james@qitest4: ~/source/perl/aitl/libs/Decurse
$ perl tests/Hierarchical.t 
1..1
ok 1 - new() does Hierarchical
Now all I have to do is fill in a bunch more tests.

But why bother?

You may be wondering why I want to formalise these tests at all; I tested it once, it worked, good - why not move on to other things? Well, the important deal with writing test cases is not to verify that your code works the first time after you've written it. The point is to be able to repeatedly verify that you haven't broken anything in the future! Perhaps you refactor some code, which changes some semantics about a method call somewhere, which has a knock-on effect in a class you'd forgotten about. Having testing code, and running those tests frequently, will ensure that you catch that badness early and tell you exactly what went wrong.

That's all for now. I hope this post encourages you to write your own testing code - it doesn't have to be a big chore, after all! Next update I will probably write about the laptop I've bought specifically for programming work, I'm very excited about it.

No comments:

Post a Comment