单元测试带有打印到屏幕的参数的Perl函数

时间:2015-05-21 02:13:57

标签: perl unit-testing

如何使用打印到屏幕的参数对perl函数进行单元测试?

sub test {
  my $var = shift;
  if( $var eq "hello") {
    print "Hello";
  }
  else {
    print "World";
  }
} 

我想完全覆盖打印到屏幕的功能中的所有条件,但我不知道如何...

我在stackoverflow How can I unit test Perl functions that print to the screen?

看到了这个答案

是的答案可以对输出字符串的函数进行单元测试,但前提是所需的函数中没有参数..在我的情况下我可以使用它:

stdout_is(\&test, "World", 'test() return World');

但我如何测试print "Hello";

修改

我尝试使用此测试用例:

should_print_hello();
should_print_world();


sub should_print_world
{
  stdout_is(\&test, "World", "should_print_world");
}

sub should_print_hello
{
  # this does not work and outputs error
  stdout_is(\&test("hello"), "Hello", "should_print_hello");
}

因为函数的stdout_is'参数只是对函数的代码引用(如果我没有记错的话),它没有function(variables_here)

我也读过perl Test::Output manual,但我仍然无法找到解决方案..还有其他方式或者我错过了什么吗?

所以我的主要问题是: 如何对仅打印到屏幕(stdout)的perl函数(带参数)进行单元测试?

2 个答案:

答案 0 :(得分:5)

您只需要在匿名子例程中包装要测试的调用

我正在使用output_is来测试stdoutstderr。如果你有use warnings,那么第四次测试应该失败

output_is(sub { test('hello') }, 'Hello', '', 'Test specific output');
output_is(sub { test('xxx') },   'World', '', 'Test non-specific output');
output_is(sub { test('') },      'World', '', 'Test null string parameter output');
output_is(sub { test() },        'World', '', 'Test no-parameter output');

答案 1 :(得分:3)

您的编辑通过指出您不了解anonymous subroutines的事实澄清了问题:

  

在运行时定义匿名子例程:

$subref = sub BLOCK;                 # no proto
$subref = sub (PROTO) BLOCK;         # with proto
$subref = sub SIG BLOCK;             # with signature
$subref = sub : ATTRS BLOCK;         # with attributes
$subref = sub (PROTO) : ATTRS BLOCK; # with proto and attributes
$subref = sub : ATTRS SIG BLOCK;     # with attribs and signature

当您撰写\&test("hello")时,perl使用参数test调用"hello",并尝试引用其返回值。

当您撰写\&test时,perl会为您提供对子例程test的引用。例如,如果您有my $f = \&test,则可以稍后使用$f->()来调用test

但是,如果您想推迟使用参数调用test,那么my $f = \&test("hello")将立即使用参数test调用"hello"并存储引用$f中的返回值。这很自然,因为跟随参数列表的子程序的名称会导致其调用。

除此之外,您可以在匿名子例程中将特定调用包装到test。如果您有my $f = sub { test("hello") },则$f中的内容是一个函数,在调用时,使用参数test调用"hello"并返回其返回值。

您可以使用它来定义closures,这将有助于减少测试中的重复:

#!/usr/bin/env perl

use strict;
use warnings;

use Test::More;
use Test::Output;

sub hello {
    print +($_[0] eq 'hello') ? 'Hello' : 'World';
}

sub bye {
    print +($_[0] eq 'bye') ? 'Bye' : 'World';
}

my @hello_tests = (
    [ [ 'hello' ] => 'Hello' => '' ],
    [ [ 'xxx' ]   => 'World' => '' ],
    [ [ ' ' ]     => 'World' => '' ],
    [ [ ]         => 'World' => '' ],
);

my @bye_tests = (
    [ [ 'bye' ]   => 'Bye'   => '' ],
    [ [ 'xxx' ]   => 'World' => '' ],
    [ [ ' ' ]     => 'World' => '' ],
    [ [ ]         => 'World' => '' ],
);

sub make_tester {
    my $f = shift;
    sub {
        my $args = shift;
        my $out = shift;
        my $err = shift;
        output_is(
            sub { $f->(@$args) },
            $out,
            $err,
            sprintf(
                "called with (%s) prints '%s' on stdout, and '%s' on stderr",
                join(',', @$args),
                $out ? $out : '*nothing*',
                $err ? $err : '*nothing*',
            )
        );
    };
}

my @test_list = (
    [ \&hello, \@hello_tests ],
    [ \&bye,   \@bye_tests ],
);

for my $atest (@test_list) {
    my $tester = make_tester($atest->[0]);
    $tester->(@$_) for @{ $atest->[1] };
}

当然,通过不明确声明@hello_tests@bye_tests并将所有内容填充到@test_list中,可以使其更加紧凑,但我认为这使得阐述更加清晰。

调用make tester生成一个匿名子例程,当使用参数列表调用时,预期的标准输出,预期的标准错误和测试描述,测试作为唯一参数传递的函数的输出。

然后,浏览测试列表,创建一个匿名子,为每个测试用例调用上面生成的函数。