如何使用打印到屏幕的参数对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函数(带参数)进行单元测试?
答案 0 :(得分:5)
您只需要在匿名子例程中包装要测试的调用
我正在使用output_is
来测试stdout
和stderr
。如果你有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生成一个匿名子例程,当使用参数列表调用时,预期的标准输出,预期的标准错误和测试描述,测试作为唯一参数传递的函数的输出。
然后,浏览测试列表,创建一个匿名子,为每个测试用例调用上面生成的函数。