使用编译测试节省时间
在过去的一年里,我一直在从事几个大型Perl项目,有时是团队项目,有时是个人项目。随着代码库的增长,测试变得越来越重要,其中一种特别有益的测试是编译测试。也就是说,在运行其他任何测试之前,只需检查代码库中的每个模块是否都能编译。
基础知识
让我们看看一个简单的编译测试,这个例子是从Perly-Bot中改编的。
#!/usr/bin/env perl
use Test::More;
use lib 'lib';
my @modules = qw(
Perly::Bot
Perly::Bot::Feed
Perly::Bot::Feed::Post
Perly::Bot::Cache
Perly::Bot::Media
Perly::Bot::Media::Twitter
Perly::Bot::Media::Reddit
);
for my $module ( @modules )
{
BAIL_OUT( "$module does not compile" ) unless require_ok( $module );
}
done_testing();
代码很简单;它将本地lib
目录添加到Perl搜索模块的目录列表中。然后它声明一个名为@modules
的模块名称数组。最后,它遍历每个模块名称并尝试导入它,如果任何模块加载失败则退出。由于测试通常是按字母顺序运行的,因此这个文件被称为00-compile.t
,以便它首先运行。我可以在终端运行这个测试。
$ ./t/00-compile.t
perl t/00-compile.t
ok 1 - use Perly::Bot;
ok 2 - use Perly::Bot::Feed;
ok 3 - use Perly::Bot::Feed::Post;
ok 4 - use Perly::Bot::Cache;
ok 5 - use Perly::Bot::Media;
ok 6 - use Perly::Bot::Media::Twitter;
ok 7 - use Perly::Bot::Media::Reddit;
1..7
一次写入的编译测试
基本的编译测试示例有一个明显的缺陷:它要求程序员列出所有要测试的模块名称。这意味着每次向代码库中添加新模块或重命名模块时,都需要更新这个测试。这也引入了错误的风险 - 一个失败的模块可能存在于代码库中,但从未被测试。而不是静态的模块列表,我可以告诉Perl搜索lib
目录并尝试导入它找到的任何模块。
#!/usr/bin/env perl
use Test::More;
use lib 'lib';
use Path::Tiny;
# try to import every .pm file in /lib
my $dir = path('lib/');
my $iter = $dir->iterator({
recurse => 1,
follow_symlinks => 0,
});
while (my $path = $iter->())
{
next if $path->is_dir || $path !~ /\.pm$/;
BAIL_OUT( "$path does not compile" ) unless require_ok( $path );
}
done_testing;
在这里,我使用Path::Tiny遍历lib
目录中的文件。我不传递模块名称,而是将文件路径传递给require_ok
。现在这个编译测试是动态的,它将始终选择代码库中添加或删除的新模块。太棒了!
要求警告
使用require通过文件路径而不是模块名称加载文件的一个问题是,如果同一模块被不同的文件加载两次,它可能会生成“子程序重新定义”警告。想象一下这个代码
require 'lib/Game.pm';
require 'lib/Game/Asset/Player.pm';
如果Game.pm
加载Game::Asset::Player
,当第二个require
语句执行时,Perl会发出“子程序重新定义”警告。我可以通过几种方式处理这个问题:我可以在编译测试文件中添加no warnings 'redefine';
来抑制警告。但这样会掩盖可能是有用的真实警告,比如我在代码库中有循环依赖。或者我可以将文件路径转换为模块名称,然后require
就不会抱怨,例如
require 'Game';
require 'Game::Asset::Player';
对于编译测试,我可以用替换正则表达式将文件路径转换为模块名称。当编译测试运行时,它们不会生成虚假的“子程序重新定义”警告。
#!/usr/bin/env perl
use Test::More;
use lib 'lib';
use Path::Tiny;
# try to import every .pm file in /lib
my $dir = path('lib/');
my $iter = $dir->iterator({
recurse => 1,
follow_symlinks => 0,
});
while (my $path = $iter->())
{
next if $path->is_dir || $path !~ /\.pm$/;
my $module = $path->relative;
$module =~ s/(?:^lib\/|\.pm$)//g;
$module =~ s/\//::/g;
BAIL_OUT( "$module does not compile" ) unless require_ok( $module );
}
done_testing;
其他想法
编写编译测试的另一种方法是使用Class::Load进行模块导入。它有几个用于动态加载模块的有用函数。
编译测试是测试类中的一个有趣类别。它是公理“代码库应始终可编译”的实现。根据应用程序的不同,您还可以测试其他公理。例如,对于Web应用程序,每个管理员URL只应允许经过认证和授权的用户访问。因此,您可以编写一个动态测试,列出每个管理员URL并尝试未经授权地获取它(如果任何请求成功,则测试失败)。对于测试Catalyst Web应用程序,您可能会发现我的模块Catalyst::Plugin::ActionPaths很有用。测试公理通常具有高回报,维护成本低或几乎没有。寻找它们吧!
如果您需要抑制特定的警告,在Perl的新版本中,warnings pragama 文档列出了它识别的所有警告类型。当使用如子程序签名这样的实验性功能时,这特别有用。您可以在命令行中使用以下命令阅读您版本Perl的版本:
$ perldoc warnings
本文最初发布在PerlTricks.com上。
标签
反馈
这篇文章有什么问题吗?通过在GitHub上打开问题或拉取请求来帮助我们。