prove is a tool for running Perl tests
prove # runs tests in 't' and 'xt' directories
prove t # runs tests in 't' directory
prove t/cowboy t/alien
prove -r t/cowboy t/alien # recursive
prove -lvr # with the 'lib' dir, verbose, recursive
prove \
--lib \ # add 'lib' dir to your $PERL5LIB
--verbose \ # more output
--recurse \ # run tests in the t dir recursively
-Pretty \ # use the prove plugin App::Prove::Plugin::retty
--jobs 10 \ # run tests in parallel
--state=slow # run slowest tests first
save your options in your ~/.proverc
--lib # add 'lib' dir to your $PERL5LIB
--verbose #
--recurse # run tests in the t dir recursively
--state=save # save test info to .prove in the cwd
-Pretty # use the prove plugin App::Prove::Plugin::retty
t
├── 00_compile.t # this test runs first
├── data # dir for test data
│ ├── alien.json
│ └── cowboy.json
├── lib # dir for test libraries
│ └── t
│ └── Helper.pm # t::Helper
├── Transmogrifier
│ ├── In
│ │ └── Tiger.t # unit tests for Transmogrifier::In::Tiger
│ └── Out
│ ├── Alien.t # unit tests for Transmogrifier::Out::Alien
│ └── Cowboy.t # unit tests for Transmogrifier::Out::Cowboy
└── Transmogrifier.t # integration tests for Transmogrifier.pm
xt # extra tests are run only during a release
├── reallyslow.t
└── slow.t
ok( $got, $test_name);
ok(!$got, $test_name);
is ($got, $expected, $test_name);
isnt($got, $expected, $test_name);
like ($got, qr/expected/, $test_name);
unlike($got, qr/expected/, $test_name);
is_deeply($got_complex_thing, $expected_complex_thing, $test_name);
isa_ok($object, $class);
# rare, but used for complex logic:
# eg, if I get to this place, pass()
pass "this test will always pass";
fail "this test will always fail";
# Used to make sure you meant to stop testing here and
# didn't exit/return/die and skip the rest of your tests.
done_testing();
SKIP: {
skip $why, $how_many unless $have_some_feature;
ok $got1, $test_name1;
is $got2, $expected, $test_name2;
like $got3, qr/$expected/, $test_name3;
};
$ prove t/tiger.t
t/tiger.t ..
ok 1 - created a tiger
ok 2 - tiger has sharp teeth
ok 3 - tiger has stripes
ok 4 # skip third party tests
ok 5 # skip third party tests
ok 6 # skip third party tests
1..6
ok
All tests successful.
use Test::More skip_all => 'third party tests';
# ...test code here...
done_testing;
$ prove t/tiger.t
t/tiger.t ..
1..0 # SKIP third party tests
skipped: third party tests
Files=1, Tests=0, 0 wallclock secs ( 0.01 usr 0.01 sys + 0.04 cusr 0.00 csys = 0.06 CPU)
Result: NOTESTS
TODO: {
local $TODO = $why;
is $got, $expected, $test_name;
};
$ prove t/tiger.t
t/tiger.t ..
ok 1 - created a tiger
ok 2 - tiger has sharp teeth
ok 3 - tiger has stripes
not ok 4 - the tiger ate # TODO third party tests
# Failed (TODO) test 'the tiger ate'
# at t/Transmogrifier/In/Tiger.t line 18.
# got: 'not implemented yet at /home/eric/code/slides-testing-in-perl/src/lib/Transmogrifier/In/Tiger.pm line 9.
# '
# expected: undef
1..4
ok
All tests successful.
Subtests are really useful and are highly recommended
subtest $name => sub {
ok $got1, $test_name1;
is $got2, $expected, $test_name2;
is $got3, $expected, $test_name3;
};
Test Anything Protocol
TAP is the stuff your tests output
# comic book noises test sequence commencing
# (this line and line before are ignored by the tap parser)
ok 1 - zap()
not ok 2 - bam()
# Failed test 'bam()'
# at xt/slow.t line 6.
not ok 3 - pow() # TODO not implemented yet
# Failed (TODO) test 'pow()'
# at xt/slow.t line 10.
ok 4 # skip because ComicBook::Noises::Batman is not installed
yargle blargle (this line is ignore by the tap parser)
ok 5 - kaboom()
1..5
note "here is a note"; # only displayed in verbose output
diag "here's what went wrong"; # always displayed
explain \%foo; # dump a data structure
Uses Text::Diff to display a diff when the test fails
eq_or_diff $got, "$line1\n$line2\n$line3\n", "testing strings";
$ prove t/boop.t
t/boop.t....1..1
not ok 1 - testing strings
# Failed test ((eval 2) at line 14)
# +---+----------------+----------------+
# | Ln|Got |Expected |
# +---+----------------+----------------+
# | 1|this is line 1 |this is line 1 |
# * 2|this is line b |this is line 2 *
# | 3|this is line 3 |this is line 3 |
# +---+----------------+----------------+
# Looks like you failed 1 test of 1.
my $person = make_person();
like $person->{name}, '/^(Mr|Mrs|Miss) \w+ \w+$/', "name ok";
like $person->{phone}, '/^0d{6}$/', "phone ok";
is scalar keys %$person, 2, "number of keys";
my $person = make_person();
if (ref($person) eq "HASH")
{
like $person->{name}, '/^(Mr|Mrs|Miss) \w+ \w+$/', "name ok";
like $person->{phone}, '/^0d{6}$/', "phone ok";
is scalar keys %$person, 2, "number of keys";
}
else {
fail "person is not a hash";
}
my $person = make_person();
if (ref($person) eq "HASH") {
like $person->{name}, '/^(Mr|Mrs|Miss) \w+ \w+$/', "name ok";
like $person->{phone}, '/^0d{6}$/', "phone ok";
my $cn = $person->{child_names};
if (ref($cn) eq "ARRAY") {
foreach my $child (@$cn) {
like($child, $name_pat);
}
}
else {
fail "child names not an array";
}
}
else {
fail "person not a hash";
}
Testing data structures
with Test::More
is doable
Testing horrible complex nested data structures
with Test::More
is painful
my $person = make_person();
my $name_re = re('^(Mr|Mrs|Miss) \w+ \w+$');
cmp_deeply
$person,
{
name => $name_re,
phone => re('^0d{6}$'),
child_names => array_each($name_re),
},
"person ok";
cmp_deeply
\@array,
[$hash1, $hash2, ignore()],
"first 2 elements are as expected, ignoring third element";
my $expected = {
name => ignore(),
dna => subsetof('human', 'mutant', 'alien'),
is_evil => bool(1),
weaknesses => set(@weaknesses), # ignores order, duplicates
powers => bag('flight', 'invisibility', 'fire'), # ignores order
weapons => any( undef, array_each(isa("Weapon")) );
home_planet => all(
isa("Planet"),
methods(
number_of_moons => any(0..100),
distance_to_earth => code(sub { return 1 if shift > 0 }),
),
),
};
cmp_deeply $superhero, $expected, "superhero hashref looks ok";
Using standard Perl exception syntax
eval { die "Something bad happened" };
warn $@ if $@;
Using Try::Tiny
try { die "Something bad happened" }
catch { warn $_ };
Using standard Perl exception syntax
eval {
die My::Exception->new(
error => 'Something bad happened',
request => $request,
response => $response,
);
};
warn $@->error if $@;
eval {
My::Exception->throw(
error => 'Something bad happened',
request => $request,
response => $response,
);
};
warn $@->error if $a;
Using Try::Tiny
try {
My::Exception->throw(
error => 'Something bad happened',
request => $request,
response => $response,
);
}
catch {
warn $_->error;
};
The standard module for testing exceptions
# Check the exception matches a given regex
like
exception { $obj->method() },
qr/Something bad happened/,
"caught the exception I expected";
# Check method() did not die
is
exception { $obj->method() },
undef,
"method() did not die";
# Check method() did not die
lives_ok { $obj->method } 'expecting to live';
Note: Test::Fatal is recommended over Test::Exception
use Test::More;
use Test::Warnings;
like
warning { $obj->method() },
qr/BEEP/,
'got a warning from method()';
use Test::More;
use Test::Warnings;
is_deeply
[ warnings { $obj->method() } ],
[
'INFO: BEEP!',
'INFO: BOOP!',
],
'got 2 warnings from method()';
use Test::More;
use Test::Warnings;
use Test::Deep;
cmp_deeply
[ warnings { $obj->method() } ],
bag(
re(qr/BEEP/),
re(qr/BOOP/),
),
'got 2 warnings from method()';
Note: Test::Warnings is recommended over Test::Warn
use Test::More;
use Test::Warnings;
pass 'yay!;
done_testing;
$ prove t/test.t
ok 1 - yay!
ok 2 - no (unexpected) warnings (via done_testing)
1..2
ok
All tests successful.
Note: Test::Warnings is recommended over Test::NoWarnings
Searches upward from the calling file for a directory 't' with a 'lib' directory inside it and adds that to the module search path (@INC).
use Test::Lib;
use t::Private::Test::Library;
instead of
use FindBin qw($Bin);
use lib "$Bin/lib";
use t::Private::Test::Library;
use Test::More;
use Test::Cmd;
my $test = Test::Cmd->new(prog => '/bin/ls', workdir => '');
# ls -l /var/log
$test->run(args => '-l /var/log');
is $test->stdout, $expected_stdout, 'standard out';
is $test->stderr, $expected_stderr, 'standard error';
is $? >> 8, 1, 'exit status';
# ls -a
$test->run(args => '-a');
is $test->stdout, $expected_stdout, 'standard out';
is $test->stderr, $expected_stderr, 'standard error';
is $? >> 8, 1, 'exit status';
done_testing;
TIP: You can strip ansi color codes from your program's output using Term::ANSIColor::colorstrip().
Tell test runners what kind of test you are
use Test::More;
use Test::DescribeMe qw/extended/;
pass "foo";
done_testing;
$ prove t/test.t
1..0 # SKIP Not running extended tests
skipped: Not running extended tests
$ EXTENDED_TESTING=1 prove xt/reallyslow.t
xt/reallyslow.t ..
ok 1 - very very slow test
1..1
ok
All tests successful.
Files=1, Tests=1, 0 wallclock secs ( 0.01 usr 0.00 sys + 0.03 cusr 0.00 csys = 0.04 CPU)
Result: PASS
See also Test::Settings and the Lancaster Consensus
Web servers are event driven.
Event driven code looks weird.
The traditional Plack::Test UI is weird.
When a request happens, a code reference (aka callback) gets run.
The traditional Plack::Test UI (widely used)
use Plack::Test;
test_psgi
app => My::WebApp->app,
client => sub {
# $cb is a callback (aka code reference)
my $cb = shift;
my $req = HTTP::Request->new(GET => "http://localhost/hello");
my $res = $cb->($req);
like $res->content, qr/Hello World/;
};
$cb is a wrapper around the PSGI app which does the following:
Alternate UI for Plack::Test (less common)
use Plack::Test;
use HTTP::Request::Common;
my $app = My::WebApp->app;
my $test = Plack::Test->create($app);
my $res = $test->request(GET "/");
like $res->content, qr/Hello World/;
No forking.
No network calls.
Makes things very fast and light.
But if you want forking and network calls there are 2 options for you:
Generate reports about test coverage
$ HARNESS_PERL_SWITCHES=-MDevel::Cover prove -lvr
$ cover
The report tells you the coverage of each of the
following categories as a percentage.
you can type this
use strict;
use warnings;
use Test::More;
use Test::Exception;
use Test::Differences;
use Test::Deep;
use Test::Warn;
or you can type this
use Test::Most;
but
you can type this
use strict;
use warnings;
use Test::More;
use Test::Fatal;
use Test::Warnings;
use Test::API;
use Test::LongString;
use Test::Deep;
use Test::Pod;
use Test::Pod::Coverage;
use Test::Version;
use Test::Moose;
use Test::CleanNamespace;
use Test::Benchmark;
use Test::Requires;
use Test::RequiresInternet;
use Test::Without::Module;
use Test::DescribeMe;
use Test::Lib;
or you can type this (well its roughly equivalent)
use Test::Modern;
but
Test::Modern doesn't work with Test::Pretty
~/code/text-vcard ⚡ prove t/vcard.t
t/vcard.t ..
# Subtest: output methods
ok 1 - as_string()
ok 2 - as_file()
ok 3 - file contents ok
1..3
ok 1 - output methods
# Subtest: simple getters
ok 1 - full_name
ok 2 - title
ok 3 - photo
ok 4 - birthday
ok 5 - timezone
ok 6 - version
1..6
ok 2 - simple getters
# Subtest: photo
ok 1 - returns a URI::http object
ok 2 - returns a URI::http object
ok 3 - photo
1..3
ok 3 - photo
# Subtest: complex getters
ok 1 - family_names()
ok 2 - given_names()
ok 3 - prefixes
ok 4 - suffixes
ok 5 - work phone
ok 6 - cell phone
ok 7 - work address
ok 8 - home address
ok 9 - work email address
ok 10 - home email address
1..10
ok 4 - complex getters
1..4
ok
All tests successful.
Files=1, Tests=4, 0 wallclock secs ( 0.03 usr 0.00 sys + 0.07 cusr 0.02 csys = 0.12 CPU)
Result: PASS
~/code/text-vcard ⚡ prove t/vcard.t
output methods
✓ as_string()
✓ as_file()
✓ file contents ok
simple getters
✓ full_name
✓ title
✓ photo
✓ birthday
✓ timezone
✓ version
photo
✓ returns a URI::http object
✓ returns a URI::http object
✓ photo
complex getters
✓ family_names()
✓ given_names()
✓ prefixes
✓ suffixes
✓ work phone
✓ cell phone
✓ work address
✓ home address
✓ work email address
✓ home email address
ok
Type this on the command line
prove -Pretty t
Or save some typing by adding this to your ~/.proverc
-Pretty
The standard module for testing exceptions for years
Use Test::Fatal instead!
# Check that the stringified exception matches given regex
throws_ok { $obj->method } qr/division by zero/, 'caught exception';
# Check an exception of the given class (or subclass) is thrown
throws_ok { $obj->method } 'Error::Simple', 'simple error thrown';
like $@->error, qr/division by zero/;
# Check method() died -- we do not care why
# dies_ok() is for lazy people; throws_ok() is best practice
dies_ok { $obj->method } 'died as expected';
# Check method() did not die
lives_ok { $obj->method } 'lived as expected';