Previous Page
Next Page

18.2. Modular Testing

Standardize your tests with Test::Simple or Test::More.

Writing tests always seems like a chore, and an unproductive chore at that: you don't have anything to test yet, so why write tests? And yet, most developers willalmost automaticallywrite driver software to test their new module in an ad hoc way:

    > cat try_inflections.pl 

    # Test my shiny new English inflections module... 
    use Lingua::EN::Inflect qw( inflect );

    # Try some plurals (both standard and unusual inflections)... 
    my %plural_of = (
        'house'         => 'houses',
        'mouse'         => 'mice',
        'box'           => 'boxes',
        'ox'            => 'oxen',
        'goose'         => 'geese',
        'mongoose'      => 'mongooses',
        'law'           => 'laws',
        'mother-in-law' => 'mothers-in-law',
    );

    # For each of them, print both the expected result and the actual inflection... 
    for my $word ( keys %plural_of ) {
        my $expected = $plural_of{$word};
        my $computed = inflect( "PL_N($word)" );

        print "For $word:\n",
              "\tExpected: $expected\n",
              "\tComputed: $computed\n";
    }

A driver like that is actually harder to write than a test suite, because you have to worry about formatting the output in a way that is easy to read. And it's much harder to use the driver than it would be to use a test suite, because every time you run it you have to wade though that formatted output and verify "by eye" that everything is as it should be:

    > perl try_inflections.pl

    For house:
        Expected: houses

        Computed: houses
    For law:
        Expected: laws
        Computed: laws
    For mongoose:
        Expected: mongooses
        Computed: mongeese
    For goose:
        Expected: geese
        Computed: geese
    For ox:
        Expected: oxen
        Computed: oxen
    For mother-in-law:
        Expected: mothers-in-law
        Computed: mothers-in-laws
    For mouse:
        Expected: mice
        Computed: mice
    For box:
        Expected: boxes
        Computed: boxes

That's also error-prone; eyes are not optimized for picking out small differences in the middle of large amounts of nearly identical text.

Rather than hacking together a driver program, it's easier to write a test program using the standard Test::Simple module. Instead of print statements showing what's being tested, you just write calls to the ok( ) subroutine, specifying as its first argument the condition under which things are okay, and as its second argument a description of what you're actually testing:

    
    > cat inflections.t


    use Lingua::EN::Inflect qw( inflect );
    use Test::Simple qw( no_plan );

    my %plural_of = (
        'mouse'         => 'mice',
        'house'         => 'houses',
        'ox'            => 'oxen',
        'box'           => 'boxes',
        'goose'         => 'geese',
        'mongoose'      => 'mongooses',
        'law'           => 'laws',
        'mother-in-law' => 'mothers-in-law',
    );

    for my $word ( keys %plural_of ) {
        my $expected = $plural_of{$word};
        my $computed = inflect( "PL_N($word)" );

        ok( $computed eq $expected, "$word -> $expected" );
    }

Test programs like this should be kept in files with a .t suffix (inflections.t, conjunctions.t, articles.t, ) and stored in a directory named t/ within your development directory for the application or module. If you set up your development directory using Module::Starter or Module::Starter::PBP (see "Creating Modules" in Chapter 17), this test directory will be set up for you automatically, with some standard .t files already provided.

Note that Test::Simple is loaded with the argument qw( no_plan ). Normally that argument would be tests => count, indicating how many tests are expected, but here the tests are generated from the %plural_of table at run time, so the final count will depend on how many entries are in that table. Specifying a fixed number of tests when loading the module is useful if you happen know that number at compile time, because then the module can also "meta-test": verify that you carried out all the tests you expected to.

The Test::Simple program is slightly more concise and readable than the original driver code, and the output is much more compact and informative:

    
    > perl inflections.t


    ok 1 - house -> houses
    ok 2 - law -> laws
    not ok 3 - mongoose -> mongooses
    #     Failed test (inflections.t at line 21)
    ok 4 - goose -> geese
    ok 5 - ox -> oxen
    not ok 6 - mother-in-law -> mothers-in-law
    #     Failed test (inflections.t at line 21)
    ok 7 - mouse -> mice
    ok 8 - box -> boxes
    1..8
    # Looks like you failed 2 tests of 8.

More importantly, this version requires far less effort to verify the correctness of each test. You just scan down the left margin looking for a "not" and a comment line.

You might prefer to use the Test::More module instead of Test::Simple. Then you can specify the actual and expected values separately, by using the is( ) subroutine, rather than ok( ):


    use Lingua::EN::Inflect qw( inflect );
    use Test::More qw( no_plan );     
# Now using more advanced testing tools
my %plural_of = ( 'mouse' => 'mice', 'house' => 'houses', 'ox' => 'oxen', 'box' => 'boxes', 'goose' => 'geese', 'mongoose' => 'mongooses', 'law' => 'laws', 'mother-in-law' => 'mothers-in-law', ); for my $word ( keys %plural_of ) { my $expected = $plural_of{$word}; my $computed = inflect( "PL_N($word)" );
# Test expected and computed inflections for string equality...
is( $computed, $expected, "$word -> $expected" ); }

Apart from no longer having to type the eq yourself[*], this version also produces more detailed error messages:

[*] The ok subroutine is still available from Test::More if you do want to specify your own comparisons.

    
    > perl inflections.t


    ok 1 - house -> houses
    ok 2 - law -> laws
    not ok 3 - mongoose -> mongooses
    #     Failed test (inflections.t at line 20)
    #          got: 'mongeese'
    #     expected: 'mongooses'
    ok 4 - goose -> geese
    ok 5 - ox -> oxen
    not ok 6 - mother-in-law -> mothers-in-law
    #     Failed test (inflections.t at line 20)
    #          got: 'mothers-in-laws'
    #     expected: 'mothers-in-law'
    ok 7 - mouse -> mice
    ok 8 - box -> boxes
    1..8
    # Looks like you failed 2 tests of 8.

The Test::Tutorial documentation that comes with Perl 5.8 provides a gentle introduction to both Test::Simple and Test::More.

    Previous Page
    Next Page