测试编写标准/指南

测试编写标准/指南

以下是改进现有测试以及创建新测试的指南。

始终指定计划

在测试开发期间使用 no_plan 是可以的,但一旦完成,或者即使您正在发送补丁/提交到存储库,也请指定您的计划。

使用 use_ok() 检查您的 use 语句。

将您的 use 语句包装在 BEGIN 块内的 use_ok() 中。

    BEGIN {
        use_ok( "MARC::Batch" ) or die;
    }

or die; 修饰符不是必需的,但如果模块只编译了一部分,即使代码正确,一些测试也会失败,它可以帮助避免混淆。此外,如果模块甚至无法编译,测试它就没有意义,对吧?

所有对象创建都使用 isa_ok() 检查。

如果您实例化一个对象,请使用 isa_ok() 检查它。

    my $batch = MARC::Batch->new( 'USMARC', @files );
    isa_ok( $batch, "MARC::Batch" );

构造函数是最明显的,但任何返回对象的函数都应该检查该对象是否为预期类型。

    while ( my $marc = $batch->next ) {
        isa_ok( $marc, "MARC::Record" );
        # more tests...
    }

在这种情况下,next() 方法应该每次都返回一个 MARC::Record

所有临时文件都已清理。

检查 unlink() 的返回值,然后检查文件是否确实消失了。

    is( unlink( $filename ), 1, "Remove $filename" );

    ok( !-e $filename, "Actually gone" );

(此习惯用法将在理想情况下使用。在 VMS 上,您可能需要多次 unlink() 同一个文件,直到它完全删除;上面的 is() 测试将仅在 $filename 实际上存在时成功,这可能不是您想要测试的。)

所有测试都应该有一个标签

所有 Test::More 函数中的最后一个参数是用于测试的标签或描述。如果您编写了一个测试,您应该知道它在做什么,所以给它贴上标签。

唯一的例外是 isa_okcan_ok,因为它们非常直观,并为您提供默认消息。

对象等价性测试

所有对象相等性测试都使用 is 完成

    is( $object, $object2, '... our objects are equal' );

对象不相等性测试可以使用 isnt 完成

    isnt( $object, $object2, '... our objects are not equal' );

这样做的原因是我们实际上是在比较字符串化的实例,而 isisnt 比较字符串。如果对象的字符串化没有提供足够的信息来有意义地比较对象,这可能会发生在重载相等或字符串化运算符(直接或间接)的情况下,找到可以做到这一点的东西(例如,使用函数 overload::StrVal())。

为工作使用正确的函数

我们应该注意我们正在进行的比较中的类型。对于比较字符串,使用 isisnt,并使用 cmp_ok 进行任何数字比较或更复杂的比较,而不是相等性。只有在正确的情况下使用正确的运算符才有意义。

这也意味着看起来像这样的测试

    ok( $cost == $expected_cost );

应该改写为

    is( $cost, $expected_cost );

或者,更好的是,因为这些值是数字

    cmp_ok( $cost, '==', $expected_cost, 'Cost calculated correctly.' );

它使测试更加明确,并利用了 isisntcmp_ok 的错误消息。

使用 is_deeply 测试除标量以外的其他内容

如果可能,我们应该明确检查数据结构的内容,而不是仅仅隐式测试元素/键计数或其他此类技术。

使用 is_deeply,您可以执行以下操作

    my @expected = qw( Larry Moe Curly );

    my @stooges = get_stooges();

    is_deeply( \@stooges, \@expected, "Got proper stooges" );

如果它们不同,您将收到一条消息,说明它们在哪里不同。

is_deeply 不合适的时候,Test::More 还包含三个实用程序函数 eq_arrayeq_hasheq_set,它们在调用时返回一个布尔值。这些可以像这样使用

    my @expected = qw( Larry Moe Curly );

    my @stooges = get_stooges();

    # use eq_set to test arrays regardless of their order

    ok(eq_set( \@stooges, \@expected), "Got proper stooges" );

is_deeply 不适合比较重载对其实现类型进行解引用的对象。

测试所有您的假设。

事后测试的假设也应该事先测试。我的意思是,如果您假设在操作完成后一个变量将为真,但在之前它为假。首先确认初始的假值,然后运行您的代码,并在之后测试真值。这在某些人看来可能有点过分,但实际上第一个测试只是为了加强第二个测试。

测试代码仍然是代码

避免(大量)使用全局变量。您不会在常规代码中这样做,那么为什么要在测试中这样做呢?

沿着这条线,应该避免这样的结构

    my $item = Object->new();
    ... test $item here

    $item = Object->new(@args);
    ... different tests for $item here

通常应该避免像这样重复使用变量。它只会使测试流程混乱。理想情况下,经过测试的实例应该被认为是单赋值变量,并且永远不会被重复使用。

所以现在上面变成了

    my $item = Object->new();
    ... test $item here

    my $item_w_args = Object->new(@args);
    ... test $item_w_args here

或者更好的是,如果你的实例之间不相互作用,就把它们放在它们自己的词法作用域中。

    CONSTRUCTOR: {
        my $obj = Object->new();
        ...
    }

    CONSTRUCTOR_WITH_ARGS: {
        my $obj = Object->new( foo => 'bar' );
        ...
    }

作者

由 Andy Lester 维护,Stevan Little 和 Rafael Garcia-Suarez 贡献。