模仿Perl的Test :: More :: done_testing最惯用的方法是什么?

时间:2010-05-18 17:26:57

标签: perl unit-testing scope

我必须在环境中使用非常旧版本的Test::More(带有$Test::More::VERSION being '0.80'的perl5.8)构建单元测试,该版本早于添加done_testing()

由于实际原因,升级到更新的测试::更多是不可能的。我试图避免使用no_tests - 当你的单元测试过早退出时通常是一个坏主意 - 比如由于某些逻辑没有按你预期的那样执行。

运行可配置数量的测试的最常用方法是什么,假设没有使用no_testsdone_testing()


详细

我的单元测试通常采用以下形式:

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");
    # ...
}

use Test::More tests => 23;BEGIN {plan tests => 23}的标准方法不起作用,因为两者都明显在@tests知道之前执行。


我目前的做法是让@tests全局并在BEGIN {}块中定义它,如下所示:

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

我觉得这可以更具惯用性但不确定如何改进。气味中最重要的是重复our @test_test声明 - 在BEGIN{}之后及之后。


另一种方法是通过调用done_testing()来模拟Test::More->builder->plan(tests=>$total_tests_calculated)。我不确定它是否更具有惯用性。

4 个答案:

答案 0 :(得分:3)

不要破解旧版本,只需附带一份Test :: More。它没有依赖关系。只需将其安装到您的发行版的t/lib(您可以构建它,然后复制blib/lib),然后在测试中use lib "t/lib"

答案 1 :(得分:1)

这是一种相当惯用的方法:

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;

使用完全限定名称可以避免使用our,如果您担心在@::test_set包中添加某些内容,也可以使用test::。使用map中的sumList::Util可以缩短BEGIN块中的代码。函数形式还反转了数据流,允许在最后声明所有测试,将plan调用保持在顶部,以提醒为什么BEGIN块在第一个中被使用的地方。

答案 2 :(得分:1)

如何使用闭包来返回测试集,这可以避免包变量的尴尬?这是一个例子:

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;
}

答案 3 :(得分:1)

如果您只需要根据测试表计算计划,那就太微不足道了。

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});
}

如果由于某种原因run_tests需要根据数据运行可变数量的测试,请使用skip而不是if,以便始终运行一致数量的测试

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;
}

对于更复杂的事情,请使用BEGIN块添加到计划中。

use Test::More;
my $Count;

BEGIN { $Count += X }

...run X tests...

BEGIN { $Count += Y }

...run Y tests...

BEGIN { plan tests => $Count }

这至少使测试计数计算与其计算的测试块保持一致,而不是将其全部放在顶部的一个大的不可维护的blob中。除了BEGIN之外,它都是高度可见的并且不需要任何魔法。

顺便提一下,新版本的Test :: More有subtest来更好地解决将测试分解为多个计划的问题。