Perl 单元测试框架的概述
随着敏捷开发模式的流行,如何快速高效地适应不确定或经常性变化的需求显得越来越重要。要做到这一点,需要在开发过程的各个阶段引入足够的测试。而其中单元测试则是保证代码质量的第一个重要关卡。
针对各种不同的语言,都有特有的单元测试框架。比如针对 Java 程序的单元测试框架 JUnit,针对 Python 程序的单元测试框架 PyUnit,针对 XML 程序的单元测试框架 XMLUnit 等。
目前,比较通用的 Perl 单元测试框架模块主要有 Test::Class 和 Test::Unit。
Test::Unit 类似于 JUnit 框架,虽然它提供了通过子类的方式扩展测试类,但由于不基于 Test::Builder,无法用到 Test::Builder 系列测试模块的强大作用。Test::Class 同样支持通过创建子类、孙子类等重用测试类以及管理测试,同时,Test::Class 是基于 Test::Builder 模块创建的,因此可以使用任何 Test::Builder 系列的测试模块,如 Test::More、Test::Exception、Test::Differences、Test::Deep 等。这是 Test::Class 相对于 Test::Unit 的一大优势。
本文将结合具体实例,介绍如何创建基于 Test::Simple、Test::More 和 Test::Class 的 Perl 单元测试框架。
模块的安装
由于 ::Simple、Test::More 和 Test::Class 都不是标准模块,因此需要安装。可以用 CPAN 方式在 root 权限下安装。命令如下:
Perl – MCPAN – e ‘ install Test::Class ’
其他模块的安装方法类似。使用 CPAN 需要连接到网络,如果当前没有网络环境,可以根据事先下载好的模块的 readme 文件中的步骤安装相应模块。
Perl 单元测试框架
本节中,我们假设有一个模块 Hello.pm 需要测试,我们结合不同的 Perl 测试框架,讨论测试代码的写法,并从测试结果介绍他们的特点和作用。
Hello.pm 的源代码如下:
清单 1. 被测对象 Hello.pm 源代码
Test::Simple
我们先来介绍最简单、最基础的模块 Test::Simple。之所以说这个模块是最简单最基础的,是因为这个模块只有一个 function ok()。语法如下:
Syntax: ok(Arg1,Arg2)
Arg1: 布尔表达式,如果这个表达式为真, 这个 testcase passed,否则 Failed;
Arg2: 这个参数是可选的,用来设置 testcase name。
因此,我们能很轻松地书写基于 Test::Simple 测试框架的测试代码。示例代码 test_simple.perl 如下:
清单 2. Test::Simple 示例代码
需要特别说明的是,在写测试脚本之前,必须事先声明计划执行的 testcase 的个数,如:
我们在命令行中执行 perl test_simple.perl,观察程序的输出如下:
清单 3. Test::Simple 示例代码执行结果
从测试结果中不难看出,第一个和第二个 testcase 成功通过测试,而第三个 testcase 则失败了。
Test::More 的介绍
从字面意思上不难看出 Test::More 比 Test::Simple 提供了更多更广泛的对 testcase 是否成功的支持。下面简单介绍其中的一些常用功能。
和 Test::Simple 一样,Test::More 同样需要事先申明需要测试的 testcase 的个数。比如:
然而,你可能在最初并不能预见到底需要测试多少个 testcase。为此 Test::More 提供了另外一种在最下方用 done_testing 的方式来达到这个目的。对应的代码如下:
这时,你甚至可以用 skip_all 来跳过 testcase。
Test::More 中提供了许多使用的方法,表 1 中列举出了其中的一些。
表 1. 常用 Test::More 方法
方法 | 说明 | 用法 | ||
---|---|---|---|---|
ok | 判断 testcase ok | ok($got op $expected,$test_name); | ||
is/isnt | 字符串比较 | is($got,$expected,85); border-right-width:0px; border-right-style:none"> | isnt($got,$test_name); | |
like/unlike | 正则表达式比较 | like( $got,qr/expected/,$test_name ); | ||
nlike( $got,85); border-right-width:0px; border-right-style:none"> cmp_ok | 可以指定操作符地比较 | cmp_ok($got,$op,85); border-right-width:0px; border-right-style:none"> can_ok | 被测模块或对象的方法 | can_ok($module,@methods) |
can_ok($object,@methods) | ||||
isa_ok | 对象是否被定义或对象的实例变量确实是已定义的引用 | isa_ok($object,$class,$object_name); | ||
isa_ok($subclass,85); border-right-width:0px; border-right-style:none"> isa_ok($ref,$type,$ref_name); | ||||
subtest | 测试子集 | subtest $name=>\&code; | ||
pass/fail | 直接给出通过 / 不通过 | pass($test_name); | ||
fail($test_name); | ||||
use_ok | 测试加载模块并导入相应符号是否成功 | BEGIN \{use_ok($module);} | ||
BEGIN \{use_ok($module,@imports);} | ||||
is_deeply | 复杂数据结构的比较 | is_deeply($got,$test_name); | ||
new_ok | 判断创建的对象是否 ok | my $obj=new_ok($class); | ||
my $obj=new_ok($class=>@args); | ||||
my $obj=new_ok($class=>@args,$object_name); |
这里,我们着重介绍其中的几个。
- is(Arg1,Arg2,Arg3)
类似于 ok(),用 eq 操作符比较 Arg1 和 Arg2 的值来决定 testcase 成功还是失败。Arg3 是指 testcase 的名字。
- like( Arg1,Arg3 )
Arg2 是一个正则表达式,比较 Arg1 是否 匹配 Arg2 正则表达式。Arg3 是指 testcase 的名字。
- cmp_ok( Arg1,Arg3,Arg4 );
cmp_ok() 允许用任何二元操作符(Arg2)比较 Arg1,Arg3. 同样,Arg4 是指 testcase 的名字。另外 cmp_ok() 有个好处,如果 testcase Failed,结果中会报告 Arg1 和 Arg2 在运行中的实际值。
- can_ok($module,@methods); can_ok($object,@methods);
更多方法可以参考 CPAN 上关于 Test::More 的更多的介绍。
基于这些函数,我们能非常方便的设计和实现基于 Test::More 的 testcase。示例代码 test_more.perl 如下:
清单 4. Test::More 示例代码
在命令行下运行 perl test_more.perl 后,我们可以看到程序的输出如下:
清单 5. Test::More 示例代码执行结果
Test::Class 的介绍
定义一个测试类,只需要编写一个从 Test::Class 继承的子类,申明如下:
由于 Test::Class 本身没有提供测试函数,而是使用 Test::More 之类的其他测试框架的方法,因此需要申明 Test::More 模块 :
- Test 方法
- sub method_name:Test \{...};
- sub method_name:Test(N)\{...};
- 列表项中可以包含代码清单,表格和图片(例如一系列图片以列表项的形式组织到一起);
N: 代表函数内测试判断执行数量,相当于执行 case 数目。默认代表只执行 1 个 case. 如果你无法判断执行 case 数目,如循环执行 case。那么,可以使用 sub method_name:Test(no_plan) \{...} 或 sub method_name:Tests\{...}。
- Setup 和 teardown 方法
- startup 和 shutdown 方法
startup 和 shutdown 方法用法和 Setup,teardown 方法类似,区别在与 Startup 和 shutdown 是在所有测试方法执行之前和之后调用。
下面的例子是基于 Test::Class 的测试代码 test_class.perl。
清单 6. Test::Class 示例代码
在命令行中执行 perl test_class.perl 后的测试结果如下:
清单 7. Test::Class 示例代码执行结果
应用实例
有了之前对于几个常用 Perl 单元测试框架的介绍,下面我们给出一个具体的实例,来测试一个 CPAN 中的一个 module File::Util。部分单元测试的代码如下。
清单 8. 应用实例代码
程序中设计了三个 testcase,分别用于测试模块中的方法的命名空间可见性、existent 方法和 line_count 方法。测试的结果如下: