Perl构建、单元测试、代码覆盖率:一个完整的工作示例

89

我在Stackoverflow上找到的大多数关于Perl构建过程、单元测试和代码覆盖率的答案都只是指向CPAN的文档。将文档放在CPAN模块中没有任何问题,因为这里应该有完整的文档。但在许多情况下,我很难找到完整的工作代码示例。

我一直在网上搜索实际的工作代码示例,可以下载或者复制粘贴到我的IDE中,就像典型的教程“Hello World”源代码一样,但它需要演示带有单元测试和代码覆盖分析的构建过程示例。有没有人有一个小的示例项目,它演示了这些技术和过程呢?

(我自己有一个小的工作示例,我会用它来回答我的问题,但可能有其他SO用户有比我想出的更好的示例。)

5个回答

111
我需要您翻译的内容如下:

花了一些时间,从不同的来源中提取小片段并将它们融合在一起,但我认为我有一个小的工作示例,可以充分向Perl新手展示Perl构建过程,包括单元测试和代码覆盖分析与报告。(我正在使用ActiveState ActivePerl v5.10.0在Windows XP Pro PC上,Module::Build, Test::More, Devel::Cover)

首先,在您的Perl项目目录下创建一个"lib"目录和一个"t"目录:

HelloPerlBuildWorld
        |
        |----------> lib
        |
        |----------> t

在"lib"目录下创建一个名为"HelloPerlBuildWorld.pm"的文本文件。此文件是您将要构建和测试的Perl模块。将以下内容粘贴到此文件中:
use strict;
use warnings;
package HelloPerlBuildWorld;

$HelloPerlBuildWorld::VERSION = '0.1';

sub hello {
   return "Hello, Perl Build World!";
}

sub bye {
   return "Goodbye, cruel world!";
}

sub repeat {
   return 1;
}

sub argumentTest {
    my ($booleanArg) = @_;

    if (!defined($booleanArg)) {
        return "null";
    }
    elsif ($booleanArg eq "false") {
        return "false";
    }
    elsif ($booleanArg eq "true") {
        return "true";
    }
    else {
        return "unknown";
    }

   return "Unreachable code: cannot be covered";
}

1;

在 "t" 目录中,创建一个名为 "HelloPerlBuildWorld.t" 的文本文件。该文件是您的单元测试脚本,将尝试完全测试上面的 Perl 模块。将以下内容粘贴到此文件中:
use strict;
use warnings;
use Test::More qw(no_plan);

# Verify module can be included via "use" pragma
BEGIN { use_ok('HelloPerlBuildWorld') };

# Verify module can be included via "require" pragma
require_ok( 'HelloPerlBuildWorld' );

# Test hello() routine using a regular expression
my $helloCall = HelloPerlBuildWorld::hello();
like($helloCall, qr/Hello, .*World/, "hello() RE test");

# Test hello_message() routine using a got/expected routine
is($helloCall, "Hello, Perl Build World!", "hello() IS test");

# Do not test bye() routine

# Test repeat() routine using a got/expected routine
for (my $ctr=1; $ctr<=10; $ctr++) {
    my $repeatCall = HelloPerlBuildWorld::repeat();
    is($repeatCall, 1, "repeat() IS test");
}

# Test argumentTest() 
my $argumentTestCall1 = HelloPerlBuildWorld::argumentTest();
is($argumentTestCall1, "null", "argumentTest() IS null test");

# Test argumentTest("true") 
my $argumentTestCall2 = HelloPerlBuildWorld::argumentTest("true");
is($argumentTestCall2, "true", "argumentTest() IS true test");

# Test argumentTest("false") 
my $argumentTestCall3 = HelloPerlBuildWorld::argumentTest("false");
is($argumentTestCall3, "false", "argumentTest() IS false test");

# Test argumentTest(123) 
my $argumentTestCall4 = HelloPerlBuildWorld::argumentTest(123);
is($argumentTestCall4, "unknown", "argumentTest() IS unknown test");

现在回到您的顶级项目目录,在那里创建一个名为 "Build.PL" 的文本文件。该文件将创建您稍后将使用的构建脚本。将以下内容粘贴到此文件中:

use strict;
use warnings;
use Module::Build;

my $builder = Module::Build->new(
    module_name         => 'HelloPerlBuildWorld',
    license             => 'perl',
    dist_abstract       => 'HelloPerlBuildWorld short description',
    dist_author         => 'Author Name <email_addy@goes.here>',
    build_requires => {
        'Test::More' => '0.10',
    },
);

$builder->create_build_script();

这些就是你需要的所有文件。现在,在项目的顶级目录下,从命令行中输入以下命令:

perl Build.PL

您将看到类似以下的内容:
Checking prerequisites...
Looks good

Creating new 'Build' script for 'HelloPerlBuildWorld' version '0.1'

现在,您应该能够使用以下命令运行单元测试:
Build test

并且看到类似于这样的东西:

Copying lib\HelloPerlBuildWorld.pm -> blib\lib\HelloPerlBuildWorld.pm
t\HelloPerlBuildWorld....ok
All tests successful.
Files=1, Tests=18,  0 wallclock secs ( 0.00 cusr +  0.00 csys =  0.00 CPU)

要使用代码覆盖分析运行单元测试,请尝试如下操作:

Build testcover

你会看到类似这样的东西:

t\HelloPerlBuildWorld....ok
All tests successful.
Files=1, Tests=18, 12 wallclock secs ( 0.00 cusr +  0.00 csys =  0.00 CPU)
cover
Reading database from D:/Documents and Settings/LeuchKW/workspace/HelloPerlBuildWorld/cover_db


----------------------------------- ------ ------ ------ ------ ------ ------
File                                  stmt   bran   cond    sub   time  total
----------------------------------- ------ ------ ------ ------ ------ ------
D:/Perl/lib/ActivePerl/Config.pm       0.0    0.0    0.0    0.0    n/a    0.0
D:/Perl/lib/ActiveState/Path.pm        0.0    0.0    0.0    0.0    n/a    0.0
D:/Perl/lib/AutoLoader.pm              0.0    0.0    0.0    0.0    n/a    0.0
D:/Perl/lib/B.pm                      18.6   16.7   13.3   19.2   96.4   17.6
 ...
[SNIP]
 ...
D:/Perl/lib/re.pm                      0.0    0.0    0.0    0.0    n/a    0.0
D:/Perl/lib/strict.pm                 84.6   50.0   50.0  100.0    0.0   73.1
D:/Perl/lib/vars.pm                   44.4   36.4    0.0  100.0    0.0   36.2
D:/Perl/lib/warnings.pm               15.3   12.1    0.0   11.1    0.0   12.0
D:/Perl/lib/warnings/register.pm       0.0    0.0    n/a    0.0    n/a    0.0
blib/lib/HelloPerlBuildWorld.pm       87.5  100.0    n/a   83.3    0.0   89.3
Total                                  9.9    4.6    2.8   11.3  100.0    7.6
----------------------------------- ------ ------ ------ ------ ------ ------


Writing HTML output to D:/Documents and Settings/LeuchKW/workspace/HelloPerlBuildWorld/cover_db/coverage.html ...
done.

(请有人告诉我如何配置Cover,使其忽略除我的单个文件之外的所有Perl库,并向我报告。我无法按照CPAN文档中的说明使Cover过滤器正常工作!)

现在,如果您刷新顶级目录,就可以看到一个名为“cover_db”的新子目录。进入该目录并双击“coverage.html”文件,在您喜欢的Web浏览器中打开代码覆盖率报告。它会给您一个漂亮的彩色超文本报告,在报告中,您可以单击文件名,查看详细的语句、分支、条件、子例程覆盖统计信息,以及实际源代码旁边的报告。您可以在此报告中看到,我们根本没有涵盖“bye()”例程,还有一行代码是无法到达的,这与我们的预期不符。

代码覆盖率报告快照
(来源:leucht.com

您可以做的另一件事是,在IDE中帮助自动化此过程,方法是创建一些更多的类似于“Build.PL”的文件,这些文件明确执行我们以上手动从命令行执行的某些构建目标。例如,我使用一个名为“BuildTest.PL”的文件,其内容如下:

use strict;
use warnings;
use Module::Build;

my $build = Module::Build->resume (
  properties => {
    config_dir => '_build',
  },
);

$build->dispatch('build');
$build->dispatch('test');

然后我设置我的IDE以执行此文件(通过“perl BuiltTest.PL”)只需单击鼠标即可自动从IDE运行我的单元测试代码,而不是手动从命令行运行。将“dispatch('test')”替换为“dispatch('testcover')”以进行自动化代码覆盖执行。键入“Build help”以获取可以从Module :: Build使用的完整构建目标列表。


1
你提出建立BuiltTest.PL的想法我不太赞同。为什么不能只编写一个脚本,执行Build buildBuild test呢? - Leon Timmermans
2
Leon,你是在建议使用Perl脚本进行命令行调用吗?如果是的话,如果有一种面向对象的方式可以像BuiltTest.PL文件中的示例那样以编程方式进行调用,我宁愿不要进行命令行调用。 - Kurt W. Leucht
1
不必要,可以看看我的回答。 - Leon Timmermans
2
Module::Build并不只是为了CPAN而存在。即使它不在CPAN上,您仍然可以从各种CPAN工具中获得所有功能。即使它是一个私有模块,您仍然可以使用相同的过程构建、测试、分发和安装它。 - brian d foy
4
为了在Devel::Cover中过滤结果,我会向$ENV{HARNESS_PERL_SWITCHES}添加选项。例如:-MDevel::Cover=+ignore,.t$,+inc,/app/lib,-select,MyModule.pm,其中/app/lib是应用程序私有库,MyModule.pm是被测试的模块。 - Michael Carman
显示剩余3条评论

14

回应Kurt,我建议这个替代品来替代他的BuiltTest.PL脚本。

use strict;
use warnings;
use Module::Build;

my $build = Module::Build->resume (
  properties => {
    config_dir => '_build',
  },
);

$build->dispatch('build');
$build->dispatch('test');

它重用了由Build.PL构建的数据库(因此假定已经运行)。


太棒了!谢谢Leon。我知道我的例子有问题,但我自己对这个Perl构建的东西还很陌生! :-) - Kurt W. Leucht

12

我在Intermediate PerlMastering Perl中都有涉及到这个内容。然而,Kurt给出了一个很好的总结。

我使用Module::Release将所有内容组合成一个发布脚本。我只需要输入一个命令,一切就会发生。


12

非常有帮助的module-starter可以生成易于使用的骨架项目,处理模块安装、文档创建和模块文件的良好布局,以及--我认为--代码覆盖支持。对于任何Perl模块相关的努力来说,这都是一个很好的起点。

另外: 使用类似于Module::Build的CPAN相关工具——即使是那些可能永远不会公开发布的模块——是一个非常好的想法


7

(声明:本人是作者)

一旦你按照上述步骤整理好了一切,你就可以迈出下一步并使用Devel::CoverX::Covered来:

  • 给定源文件,列出提供覆盖该源文件的测试文件。这可在文件、子例程和行级别上完成。
  • 给定测试文件,列出由该测试文件覆盖的源文件和子例程。
  • 给定源文件,高效地报告每行或子例程的覆盖细节。

请参见概要以获取具体的命令行示例。

Devel::PerlySense中,Emacs支持在源代码缓冲区中显示覆盖信息(屏幕截图),并导航到/从覆盖测试文件。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接