Какой самый идиоматичный способ эмулировать Perl Test :: More :: done_testing? - PullRequest
3 голосов
/ 18 мая 2010

Мне нужно создать модульные тесты для среды с очень старой версией Test::More (perl5.8 с $Test::More::VERSION being '0.80'), которая предшествует добавлению done_testing().

Обновление до новой версии Test :: Больше не обсуждается по практическим соображениям. И я стараюсь избегать использования no_tests - это вообще плохая идея - не ловить, когда ваш модульный тест завершается преждевременно - скажем, из-за какой-то логики, не выполняющейся, когда вы ожидали.

Какой самый идиоматичный способ выполнения настраиваемого количества тестов , при условии, что не используется no_tests или done_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 ]

3 голосов
/ 18 мая 2010

Не взламывайте старые версии, просто отправьте копию Test :: More. У него нет зависимостей. Просто установите его в t/lib вашего дистрибутива (вы можете создать его, а затем скопировать blib/lib), а затем use lib "t/lib" в своих тестах.

1 голос
/ 20 мая 2010

Если все, что вам нужно, это рассчитать план на основе таблицы тестов, это тривиально.

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 }

Это, по крайней мере, сохраняет вычисления подсчета тестов в соответствии с блоком тестов, которые он рассчитывает, или вместо того, чтобы все это находилось в одном большом не поддерживаемом объекте в верхней части. Все это хорошо видно и не требует магии, кроме BEGIN.

Кстати, новые версии Test :: More имеют subtest, чтобы лучше справляться с проблемой разбивки теста на несколько планов.

1 голос
/ 19 мая 2010

Как насчет использования замыкания для возврата наборов тестов, что позволяет избежать неловкости переменной пакета? Вот иллюстрация:

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;
}
1 голос
/ 19 мая 2010

Вот довольно идиоматический подход:

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 и sum из List::Util немного сокращает код в блоке BEGIN. Функциональная форма также инвертирует поток данных, что позволяет объявлять все тесты в конце, сохраняя вызов plan наверху, чтобы напомнить, почему блок BEGIN использовался в первую очередь.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...