【问题标题】:Testing Perl module with Test::More (Intermediate Perl, chapter 14)使用 Test::More 测试 Perl 模块(Perl 中级,第 14 章)
【发布时间】:2012-10-19 00:07:05
【问题描述】:

这是我对 Stack Overflow 的第一个问题。如果我违反了一些规则,请提前道歉。

我一直在阅读 Intermediate Perl,第 2 版的第 14 章,其中讨论了测试 Perl 模块和使用 Test::More 的功能。我指的是直接在本书“添加我们的第一个测试”一节中发布的代码。

对于一些背景知识,在本章中,在同名模块中创建了一个示例Animal 类。这个类有一个简单的speak 方法,看起来像这样:

sub speak {
    my $class = shift;
    print "a $class goes ", $class->sound, "!\n";
}

sound 方法是为特定动物返回的简单字符串,例如,马的 sound 方法将简单地为 sub sound { "neigh" },它的 speak 方法应输出以下内容:

A Horse goes neigh!

我遇到的问题如下:在我在 ./Animal/t/Animal.t 创建的测试代码中,我被指示使用裸块和 Test::More::is 来测试 @987654330 @ 方法有效。测试文件中的代码如下所示:

[test code snip]
{
    package Foofle;
    use parent qw(Animal);

    sub sound { 'foof' }
    is( Foofle->speak, 
        "A Foofle goes foof!\n", 
        "An Animal subclass does the right thing"
    );
}

测试失败。我运行了所有的构建命令,但是在运行“构建测试”时,动物测试失败了:

Undefined subroutine &Foofle::is called at t/Animal.t line 28.

当我尝试显式使用 Test::More::is 而不是简单的 is 时,测试仍然失败并显示以下消息:

#   Failed test 'An Animal subclass does the right thing'
#   at t/Animal.t line 28.
#          got: '1'
#     expected: 'A Foofle goes foof!
# '

我的方法似乎完全按照我解释的方式定义。我认为第一个错误是由于裸块导致的范围问题,但不是 100% 肯定。第二个错误我不确定,因为如果我要创建一个 Foofle 类作为 Animal 的子类并在其上调用 speak,我不会得到 1 响应,而是得到预期的输出。

有人可以帮助我解决我可能做错的事情吗?对于可能相关的软件版本,我使用的是 perl v5.16、Test::More v0.98 和 Module::Starter v1.58。

【问题讨论】:

  • 一个很好的问题,格式良好并包含适当的细节。欢迎来到 SO

标签: perl testing module


【解决方案1】:

您已经非常正确地解释了第一个错误的原因,并且正确地修复了它(指定正确的包名称)。但是您似乎忽略了一个简单的事实:Animal 类的speak 方法没有return 这个a $class goes... 字符串 - 它返回打印它的结果(即1)!

看,这个子程序:

sub speak {
    my $class = shift;
    print "a $class goes ", $class->sound, "!\n";
}

... 没有明确的 return 语句。在这种情况下,返回的是评估子例程的最新调用语句的结果 - 即评估 print something 的结果,其中 is 1(实际上是true)。

这就是测试失败的原因。您可以通过测试1 来修复它(但我想这太微不足道了)或更改方法本身,以便它返回一个打印的字符串。例如:

sub speak {
    my $class = shift;
    my $statement = "a $class goes " . $class->sound . "!\n";
    print $statement;
    return $statement;
}

...而且,坦率地说,这两种方法看起来都有点...可疑。后者虽然显然更完整,但实际上并不会涵盖此speak 方法的所有功能:它不仅测试语句是否正确,而且不测试它是否被打印。 )

【讨论】:

  • 有趣,谢谢!我应该返回字符串而不是打印它。我必须仔细检查,但我认为这可以作为本书的勘误表。
【解决方案2】:

您已经发现,您拨打is 的问题在于您拨打电话时使用了错误的包裹。完全指定函数名称,就像通过说将is 导入命名空间一样

use Test::More;

测试包中的某处。

其余问题的答案在于您正在测试的内容和正在做的事情之间的差异。 speak 所做 是打印,但是当您询问 is(speak, ...) 时,您是在询问 speak 返回的内容,这与打印的内容无关。其实是print这个不是很有用的返回值。

因为speak 的目的是打印一个特定的字符串,所以对speak 的测试应该测试它确实打印了一个字符串并且它是正确的字符串。但是,要让测试做到这一点,您需要一些方法来捕获打印的内容。

实际上有几种方法可以做到这一点,从使用 IO::File 强制您指定要打印到的文件句柄到猴子修补 print 的替代品到您的类中,但以下技术不'不需要对被测系统进行任何修改以提高其可测试性。

select 内置允许您更改print 的打印位置。默认输出通道是STDOUT,尽管您通常应该假装您不知道这种事情。幸运的是,您也可以使用select 来发现原始文件句柄,尽管您可能应该确保恢复默认文件句柄(毕竟,这是一个全局变量),即使您的测试由于某种原因而终止。所以你需要管理异常。而且您需要一个文件句柄,您可以检查其中的内容,而不必实际打印任何内容; IO::Scalar 可以提供帮助。

通过这种方法,您最终能够使用

测试原始代码
package AnimalTest;
use IO::Scalar;

use Test::More tests => 1;
use Try::Tiny;

{
    package Foofle;
    use base qw(Animal);

    sub sound { 'foof' }
}

{
    my $original_FH = select;
    try {
        my $result;
        select IO::Scalar->new(\$result);

        Foofle->speak();
        is(
            $result, "A Foofle goes foof!\n",
            "An Animal subclass does the right thing"
        );
    } catch {
        die $_;
    } finally {
        select $original_FH;
    };
}

Try::Tiny 确保您不会乱扔垃圾,如果 speak 碰巧给 Animal 一个动脉瘤,print 被重定向以修改标量而不是实际打印到屏幕上,现在测试失败出于正确的原因,即:字符串大小写不匹配。

您会注意到其中涉及很多设置;这是因为被测系统的可测试性设置不是特别好,因此我们必须进行补偿。在我自己的代码中,这不是我会选择的方法,而是选择使原始代码更可测试。然后为了测试,我经常使用 TMOE 进行猴子补丁(即覆盖被测方法之一)。这种方法看起来更像这样:

[在动物中:]

sub speak {
    my $class = shift;
    $class->print("a $class goes ", $class->sound, "!\n");
}

sub print {
    my $class = shift;
    print @_;
}

[稍后:]

{
    package Foofle;
    use base qw(Animal);

    sub sound { 'foof' }

    sub print {
        my ($self, @text) = @_;

        return join '', @text;
    }

}

is(
    Foofle->speak(), "A Foofle goes foof!\n",
    "An Animal subclass does the right thing"
);

您会注意到这看起来更像您的原始代码。主要区别在于,Animal 不是直接调用内置的print,而是调用$class->print,而后者又调用了内置的print。子类Foofle 然后覆盖print 方法以返回其参数而不是打印它们,这使测试代码可以访问打印的内容。

这种方法比必须修改全局变量来确定要打印的内容要干净得多,但它确实有两个缺点:它需要修改被测代码以使其更具可测试性,并且它从不实际测试是否打印发生。它只是测试print 是用正确的参数调用的。因此,Animal::print 必须如此微不足道,以至于通过检查显然是正确的。

【讨论】:

  • 这个答案比我预期的要长,因为我发现我想同时提供两种选择。
【解决方案3】:

我想象你的代码看起来像:

package SomeTest;   # if omitted, it's like saying "package main"
use Test::More;
...
{
    package Foofle;
    is( something, something_else );
}

use Test::More 语句会将Test::More 的一些函数导出到调用命名空间,在本例中为SomeTest(或main)。这意味着将为符号main::ismain::okmain::done_testing等定义函数。

在以package Foofle 开头的块中,您现在位于Foofle 命名空间中,因此现在Perl 将查找与符号Foofle::is 对应的函数。它不会找到一个,所以它会抱怨并退出。

一种解决方法是将Test::More 也导入Foofle 的命名空间。

{
    package Foofle;
    use Test::More;
    is( something, something_else );
}

还有一种是使用完全限定的方法名来调用is

{
    package Foofle;
    Test::More::is( something, something_else );
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-08-22
    • 1970-01-01
    • 1970-01-01
    • 2020-03-14
    • 2010-09-07
    • 1970-01-01
    • 2012-09-18
    • 1970-01-01
    相关资源
    最近更新 更多