Reputation: 129481
I have to build unit tests for in environment with a very old version of Test::More
(perl5.8 with $Test::More::VERSION being '0.80'
) which predates the addition of done_testing()
.
Upgrading to newer Test::More is out of the question for practical reasons. And I am trying to avoid using no_tests
- it's generally a bad idea not catching when your unit test exits prematurely - say due to some logic not executing when you expected it to.
What is the most idiomatic way of running a configurable amount of tests, assuming no no_tests
or done_testing()
is used?
Details:
My unit tests usually take the form of:
use Test::More; my @test_set = ( [ "Test #1", $param1, $param2, ... ] ,[ "Test #1", $param1, $param2, ... ] # ,... ); foreach my $test (@test_set) { run_test($test); } sub run_test { # $expected_tests += count_tests($test); ok(test1($test)) || diag("Test1 failed"); # ... }
The standard approach of use Test::More tests => 23;
or BEGIN {plan tests => 23}
does not work since both are obviously executed before @tests
is known.
My current approach involves making @tests
global and defining it in the BEGIN {}
block as follows:
use Test::More; BEGIN { our @test_set = (); # Same set of tests as above my $expected_tests = 0; foreach my $test (@tests) { my $expected_tests += count_tests($test); } plan tests => $expected_tests; } our @test_set; # Must do!!! Since first "our" was in BEGIN's scope :( foreach my $test (@test_set) { run_test($test); } # Same sub run_test {} # Same
I feel this can be done more idiomatically but not certain how to improve. Chief among the smells is the duplicate our @test_test
declarations - in BEGIN{}
and after it.
Another approach is to emulate done_testing()
by calling Test::More->builder->plan(tests=>$total_tests_calculated)
. I'm not sure if it's any better idiomatically-wise.
Upvotes: 3
Views: 239
Reputation: 165145
If all you need is to calculate the plan based on a table of tests, that's trivial.
use Test::More;
my $Asserts_Per_Set = 10;
my %Tests = (
"Test #1" => { foo => "bar", this => "that" },
"Test #2" => { foo => "yar", this => 42 },
...
);
plan tests => keys %Tests * $Asserts_Per_Set;
for my $name (keys %Tests) {
run_tests($name, $Tests{$name});
}
If for some reason run_tests
needs to run a variable number of tests based on the data, make use of skip
rather than an if
so it always runs a consistent number of tests.
SKIP: {
skip "Can't run foo test on frobnitz", 2 if $test->{foo} and $test->{frobnitz};
is foo(), $test->{foo};
is bar(), $test->{foo} + 9;
}
For anything more complicated, add to the plan as you go by making use of BEGIN
blocks.
use Test::More;
my $Count;
BEGIN { $Count += X }
...run X tests...
BEGIN { $Count += Y }
...run Y tests...
BEGIN { plan tests => $Count }
This at least keeps the test count calculations in line with the block of tests its calculating or, instead of having it all in one big unmaintainable blob at the top. Its all highly visible and requires no magic except BEGIN
.
Incidentally, new versions of Test::More have subtest
to better handle the problem of breaking up a test into multiple plans.
Upvotes: 1
Reputation: 42421
How about using a closure to return the test sets, which allows you to avoid the awkwardness of a package variable? Here's an illustration:
use strict;
use warnings;
use Test::More;
BEGIN {
my @ts = (
[ 'Test 1', 1, 1 ],
[ 'Test 2', 3, 3 ],
);
plan tests => scalar @ts;
sub test_sets { return @ts }
}
for my $ts ( test_sets() ){
run_test($ts);
}
sub run_test {
my ($msg, $val, $exp) = @{shift()};
is $val, $exp, $msg;
}
Upvotes: 1
Reputation: 40152
Here's a fairly idiomatic approach:
use warnings;
use strict;
use Test::More;
use List::Util 'sum';
sub count_tests {1}
BEGIN {
plan tests => sum map {
count_tests($_)
} @test::set = (
[ "Test #1", '$param1, $param2, ...' ],
[ "Test #1", '$param1, $param2, ...' ],
)
}
run_test($_) for @test::set;
Using a fully qualified name avoids the need for our
, you could also use @::test_set
if you are worried about putting something in the test::
package. And using map
and sum
from List::Util
shortens the code in the BEGIN
block a bit. The functional form also inverts the flow of data, which allows all of the tests to be declared at the end, keeping the plan
call at the top, to remind why the BEGIN
block was used in the first place.
Upvotes: 1
Reputation: 165145
Don't hack around old versions, just ship with a copy of Test::More. It has no dependencies. Simply install it into t/lib
of your distribution (you can build it and then copy blib/lib
) and then use lib "t/lib"
in your tests.
Upvotes: 3