These are the topics I plan to cover today. This won't cover everything you need to know about unit testing in Perl, but it will give you more than the intro talk.
1..4
ok 1 - Initial sanity verified
ok 2 - Sanity still exists
not ok 3 - Sanity has left the building
# Failed test 'Sanity has left the building'
# at examples/sanity.t line 11.
# got: '4'
# expected: '5'
ok 4 - Sanity has been restored
# Looks like you failed 1 test of 4.
This is a very simple example of the output from a TAP-based test. It shows all of the major parts of a TAP test.
#
This just sets up some definitions so we will be on the same page when I begin talking about tests. A test usually requires some quantity of setup, the execution of code, and then one or more assertions about the result of the code.
The most basic assertion tool: ok()
ok()
is the simplest assertion in the Test::More
toolkit. If it's first argument is true, it prints ok. Otherwise, it
prints not ok.
ok
#!/usr/bin/perl
use Test::More tests => 4;
use strict;
use warnings;
my $var = 1;
ok( $var == 1, 'Initial sanity verified' );
ok( $var =~ m/^\d+$/, 'Sanity still exists' );
ok( 2+2 == 5, 'Sanity has left the building' );
ok( 2+2 != 5, 'Sanity has been restored' );
It only supplies the ability to set the test plan and a single function
ok()
. The ok()
function tests a scalar value,
printing ok on true and not ok for a false value.
It also provides the assertion number automatically, and adds a label if provided as a second argument.
ok
1..4
ok 1 - Initial sanity verified
ok 2 - Sanity still exists
not ok 3 - Sanity has left the building
ok 4 - Sanity has been restored
# Looks like you failed 1 test of 4.
The ok()
function has two related flaws.
As you can see here, you really don't know anything about why assertion 3 failed.
is( $actual, $expected, $name )
isnt( $actual, $expected, $name )
like( $actual, $regex, $name )
unlike( $actual, $regex, $name )
Test::More
provides several wrappers around ok
that do a much better job of expressing intent and providing troubleshooting
information. These are the most obvious ones.
is()
and isnt()
use string comparisons. Most
of the time that doesn't matter. But, once in a while you need to know.
#!/usr/bin/perl
use Test::More tests => 4;
use strict;
use warnings;
my $var = 1;
is( $var, 1, 'Initial sanity verified' );
like( $var, qr/^\d+$/, 'Sanity still exists' );
is( 2+2, 5, 'Sanity has left the building' );
isnt( 2+2, 5, 'Sanity has been restored' );
These assertions are more expressive. These don't really do much more
than the straight ok()
function if the assertion succeeds.
But, if the assertion fails, they provide more useful diagnostic
output.
1..4
ok 1 - Initial sanity verified
ok 2 - Sanity still exists
not ok 3 - Sanity has left the building
# Failed test 'Sanity has left the building'
# at examples/sanity.t line 11.
# got: '4'
# expected: '5'
ok 4 - Sanity has been restored
# Looks like you failed 1 test of 4.
As you can see, the failure condition actually gives useful output this time. If I had given actual useful names to the assertions, you might even be able to tell what the problem is from here.
use_ok
require_ok
These assertions are not nearly as useful as they appear at first glance.
They attempt to use
(or require
) the specified module.
A successful load passes the assertion and unsuccessful load, fails. This seems
relatively straight-forward and reasonable.
The two points that fall down are:
use_ok
happens at runtime, unlike use
.is_deeply( $actual, $expect, $name )
cmp_ok( $actual, $op, $expect, $name )
isa_ok( $actual, $type, $name )
can_ok( $obj_or_class, @methods )
#!/usr/bin/perl
use Test::More tests => 4;
use strict;
use warnings;
my %hash = qw/c 3 b 2 a 1/;
is_deeply( {a=>1, b=>2, c=>3}, \%hash, 'hash items' );
cmp_ok( 4, '<', 5, 'integer ordering' );
isa_ok( \%hash, 'HASH', '%hash' );
can_ok( 'Test::More', qw/ok is isnt like unlike/ );
Use of all of these are pretty much as you might expect.
diag()
- display output as comments in TAPnote()
- display output only when verboseexplain()
- dumper-like toolThese are sometimes used to display information that the maintainer of the suite may find interesting. Unfortunately, this information is not always as useful the 400th time you see it.
Each assertion returns a true value on success and false on failure.
This is a feature of the assertions that can sometimes be exploited to make your test suites even more useful.
#!/usr/bin/perl
use Test::More tests => 1;
use strict;
use warnings;
my %hash = qw/c 3 b 2 a 1/;
# Pretend that $output came from a function under test.
my $output = {a=>1, b=>2, c=>3};
is_deeply( $output, \%hash, 'hash items' )
or note explain $output;
The or note explain idiom is much more useful than what we normally do.
print Dumper( $output )
use Data::Dumper
we forgotDon't assume the person troubleshooting a failure knows why you are making this assertion.
Test::NoWarnings
Test::Warn
Test::Exception
When thinking about unit testing at a high level, you being to see certain patterns and practices emerge.
die
stops entire test fileBAIL_OUT()
to stop entire runThe simplest and most common failure in a test suite manifests as one (or a few) individual assertion failures. These are normally pretty easy to localize. Something changed. Find and fix.
Sometimes there is a failure that pretty much makes the rest of the test
file immaterial. You need to perform some tests on a database and
connection fails. No sense in going any further. Using die
to
fail the whole test file is appropriate here.
Finally, there is the rare circumstance where there's a problem so
severe that you need to abort the entire test suite. In that case, the
BAIL_OUT()
function is the right choice.
#!/usr/bin/perl
use Test::More tests => 4;
use strict;
use warnings;
SKIP: {
skip 'Root access needed', 2 if $< != 0;
ok( do_root_action(), 'Root does action' );
is( get_root_information(), 'root stuff',
'Root gets info' );
}
ok( perform_unprivileged(), 'Any user action' );
is( get_information(), 'unprivileged',
'Get normal information' );
Sometimes you need to run tests in only certain circumstances. The
skip
function allows you to bypass assertions, treating
them as successful for the sake of the test suite.
#!/usr/bin/perl
use Test::More ($^O =~ /win32/i)
? (skip_all => 'Cannot test under windows')
: (tests => 4));
If all of the assertions in a given file must be bypassed for the same reason, skip_all is the better tool.
#!/usr/bin/perl
use Test::More tests => 4;
use strict;
use warnings;
ok( working_method(), 'this works' );
TODO: {
local $TODO = 'foo method is not finished';
ok( !foo(), 'foo with no arguments' );
is_deeply( foo(qw/a b c/), [qw/C B A/],
'foo with arguments' );
}
ok( other_working_method(), 'this does too' );
These tests serve as reminders that we need to write more functionality. However, they are not counted as failures, even though the assertions do not succeed. Just as importantly, they also report if they begin functioning.
no_plan
done_testing()
tests
plan()
The no_plan option is a really bad idea. You can accidentally
bypass or miss assertions without any indication from the test harness. The
done_testing
option is safer and becoming more popular. However,
it also allows the possibility of skipped assertions without any indication.
Explicitly setting the number of tests is the safest option. But it is more work to keep it properly configured when building your test file.