home | O'Reilly's CD bookshelfs | FreeBSD | Linux | Cisco | Cisco Exam  


Learning Perl Objects, References & ModulesLearning Perl Objects, References & ModulesSearch this book

14.3. Writing Tests with Test::More

Like Test::Simple, Test::More is included with the distribution starting with Perl 5.8. The Test::More module is upward-compatible with Test::Simple, so you can simply change the module name to start using it. In this example so far, you can use:

use Test::More tests => 4;

ok(1 + 2 == 3, '1 + 2 == 3');
ok(2 * 4 == 8, '2 * 4 == 8');
my $divide = 5 / 3;
ok(abs($divide - 1.666667) < 0.001, '5 / 3 == (approx) 1.666667');
my $subtract = -3 + 3;
ok(($subtract eq "0" or $subtract eq "-0"), '-3 + 3 == 0');

You get nearly the same output you got with Test::Simple, but there's that nasty little 4 constant in the first line. That's fine once shipping the code, but if you're testing, retesting, and adding more tests, it can be a bit painful to keep the number in sync with the data. You can change that to no_plan,[100] as in:

[100]You can do this with Test::Simple as well.

use Test::More "no_plan";        # during development

ok(1 + 2 == 3, '1 + 2 == 3');
ok(2 * 4 == 8, '2 * 4 == 8');
my $divide = 5 / 3;
ok(abs($divide - 1.666667) < 0.001, '5 / 3 == (approx) 1.666667');
my $subtract = -3 + 3;
ok(($subtract eq "0" or $subtract eq "-0"), '-3 + 3 == 0');

The output is now rearranged:

ok 1 - 1 + 2 == 3
ok 2 - 2 * 4 == 8
ok 3 - 5 / 3 == (approx) 1.666667
ok 4 - -3 + 3 == 0
1..4

Note that the number of tests are now at the end. The test harness knows that if it doesn't see a header, it's expecting a footer. If the number of tests disagree or there's no footer (and no header), it's a broken result. You can use this while developing, but be sure to put the final number of tests in the script before you ship it as real code.

But wait: there's more (to Test::More). Instead of a simple yes/no, you can ask if two values are the same:

use Test::More "no_plan";

is(1 + 2, 3, '1 + 2 is 3');
is(2 * 4, 8, '2 * 4 is 8');

Note that you've gotten rid of numeric equality and instead asked if "this is that." On a successful test, this doesn't give much advantage, but on a failed test, you get much more interesting output. The result of this:

use Test::More "no_plan";

is(1 + 2, 3, '1 + 2 is 3');
is(2 * 4, 6, '2 * 4 is 6');

is the interesting:

ok 1 - 1 + 2 is 3
not ok 2 - 2 * 4 is 6
#     Failed test (1.t at line 4)
#          got: '8'
#     expected: '6'
1..2
# Looks like you failed 1 tests of 2.

Of course, this is an error in the test, but note that the output told you what happened: you got an 8 but were expecting a 6.[101] This is far better than just "something went wrong" as before. There's also a corresponding isnt( ) when you want to compare for inequality rather than equality.

[101]More precisely: you got an '8' but were expecting a '6'. Did you notice that these are strings? The is test checks for string equality. If you don't want that, just build an ok test instead. Or try cmp_ok, coming up in a moment.

What about that third test, where the value had to be less than a tolerance? Well, just use the cmp_ok routine instead:

use Test::More "no_plan";

my $divide = 5 / 3;
cmp_ok(abs($divide - 1.666667), '<' , 0.001,
  '5 / 3 should be (approx) 1.666667');

If the test given in the second argument fails between the first and third arguments, then you get a descriptive error message with both of the values and the comparison, rather than a simple pass/fail value as before.

How about that last test? You wanted to see if the result was a 0 or minus 0 (on the rare systems that give back a minus 0). You can do that with the like function:

use Test::More "no_plan";

my $subtract = -3 + 3;
like($subtract, qr/^-?0$/, '-3 + 3 == 0');

Here, you'll take the string form of the first argument and attempt to match it against the second argument. The second argument is typically a regular expression object (created here with qr) but can also be a simple string, which is converted to a regular expression object. The string form can even be written as if it was (almost) a regular expression:

like($subtract, q/^-?0$/, '-3 + 3 == 0');

The advantage to using the string form is that it is portable back to older Perls.[102]

[102]The qr// form wasn't introduced until Perl 5.005.

If the match succeeds, it's a good test. If not, the original string and the regex are reported along with the test failure. You can change like to unlike if you expect the match to fail instead.

For object-oriented modules, you might want to ensure that object creation has succeeded. For this, isa_ok and can_ok give good interface tests:

use Test::More "no_plan";

use Horse;
my $trigger = Horse->named("Trigger");
isa_ok($trigger, "Horse");
isa_ok($trigger, "Animal");
can_ok($trigger, $_) for qw(eat color);

This results in:

ok 1 - The object isa Horse
ok 2 - The object isa Animal
ok 3 - Horse->can('eat')
ok 4 - Horse->can('color')
1..4

Here you're testing that it's a horse, but also that it's an animal, and that it can both eat and return a color.[103]

[103]Well, you're testing to see that it can('eat') and can('color'). You haven't checked whether it really can use those method calls to do what you want!

You could further test to ensure that each horse has a unique name:

use Test::More "no_plan";

use Horse;

my $trigger = Horse->named("Trigger");
isa_ok($trigger, "Horse");

my $tv_horse = Horse->named("Mr. Ed");
isa_ok($tv_horse, "Horse");

# Did making a second horse affect the name of the first horse?
is($trigger->name, "Trigger", "Trigger's name is correct");
is($tv_horse->name, "Mr. Ed", "Mr. Ed's name is correct");
is(Horse->name, "a generic Horse");

The output of this is:

ok 1 - The object isa Horse
ok 2 - The object isa Horse
ok 3 - Trigger's name is correct
ok 4 - Mr. Ed's name is correct
not ok 5
#     Failed test (1.t at line 13)
#          got: 'an unnamed Horse'
#     expected: 'a generic Horse'
1..5
# Looks like you failed 1 tests of 5.

Oops! Look at that. You wrote a generic Horse, but the string really is an unnamed Horse. That's an error in the test, not in the module, so you should correct that test error and retry. Unless, of course, the module's spec actually called for 'a generic Horse'.

Again, don't be afraid to just write the tests and test the module. If you get either one wrong, the other will generally catch it.

Even the use can be tested by Test::More:

use Test::More "no_plan";

BEGIN { use_ok("Horse") }

my $trigger = Horse->named("Trigger");
isa_ok($trigger, "Horse");
# .. other tests as before ..

The difference between doing this as a test and doing it as a simple use is that the test won't completely abort if the use fails, although many other tests are likely to fail as well. It's also counted as one of the tests, so you get a "test succeeded" for free even if all it does is compile properly to help pad your success numbers for the weekly status report.

The use is placed inside a BEGIN block so any exported subroutines are properly declared for the rest of the program, as recommended by the documentation. For most object-oriented modules, this won't matter because they don't export subroutines.



Library Navigation Links

Copyright © 2003 O'Reilly & Associates. All rights reserved.