Perl 脚本中单元测试
2014-12-06 11:24
162 查看
原文链接: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 权限下安装。命令如下:
其他模块的安装方法类似。使用 CPAN 需要连接到网络,如果当前没有网络环境,可以根据事先下载好的模块的 readme 文件中的步骤安装相应模块。
回页首
本节中,我们假设有一个模块 Hello.pm 需要测试,我们结合不同的 Perl 测试框架,讨论测试代码的写法,并从测试结果介绍他们的特点和作用。
Hello.pm 的源代码如下:
清单 1. 被测对象 Hello.pm 源代码
我们先来介绍最简单、最基础的模块 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::Simple 提供了更多更广泛的对 testcase 是否成功的支持。下面简单介绍其中的一些常用功能。
和 Test::Simple 一样,Test::More 同样需要事先申明需要测试的 testcase 的个数。比如:
然而,你可能在最初并不能预见到底需要测试多少个 testcase。为此 Test::More 提供了另外一种在最下方用 done_testing 的方式来达到这个目的。对应的代码如下:
这时,你甚至可以用 skip_all 来跳过 testcase。
Test::More 中提供了许多使用的方法,表 1 中列举出了其中的一些。
表 1. 常用 Test::More 方法
这里,我们着重介绍其中的几个。
is(Arg1, Arg2, Arg3)
类似于 ok(), 用 eq 操作符比较 Arg1 和 Arg2 的值来决定 testcase 成功还是失败。Arg3 是指 testcase 的名字。
like( Arg1, Arg2, Arg3 )
Arg2 是一个正则表达式 , 比较 Arg1 是否 匹配 Arg2 正则表达式。Arg3 是指 testcase 的名字。
cmp_ok( Arg1, Arg2, Arg3, Arg4 );
cmp_ok() 允许用任何二元操作符(Arg2)比较 Arg1,Arg3. 同样,Arg4 是指 testcase 的名字。另外 cmp_ok() 有个好处,如果 testcase failed, 结果中会报告 Arg1 和 Arg2 在运行中的实际值。
can_ok($module, @methods); can_ok($object, @methods);
can_ok() 判断模块 $module 或对象 $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::More 之类的其他测试框架的方法,因此需要申明 Test::More 模块 :
Test::Class 的常用方法包括:
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 方法
setup 和 teardown 分别在每个普通测试方法之前和之后调用。
Setup 用法
sub method_name:Test(setup) \{...};
sub method_name:Test(setup=>N)\{...};
teardown 用法
sub method_name : Test(teardown) \{ ... };
sub method_name : Test(teardown => N) \{ ... };
startup 和 shutdown 方法
startup 和 shutdown 方法用法和 Setup,teardown 方法类似,区别在与 Startup 和 shutdown 是在所有测试方法执行之前和之后调用。
Runttests 方法
通过调用 runtests() 方法,执行所有从 Test::Class 派生的子类中定义的测试 case,用法:
Test::Class->runtests();
下面的例子是基于 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 方法。测试的结果如下:
清单 9. 应用实例代码执行结果
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 源代码
use strict; use warnings; package Hello; $Hello::VERSION = '0.1'; sub hello { my ($you)=@_; return "Hello, $you!"; } sub bye { my ($you)=@_; return "Goodbye, $you!"; } 1;
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 示例代码
use strict; use warnings; use Test::Simple tests => 3; use Hello; # What you're testing. my $hellostr=Hello::hello('guys'); my $byestr=Hello::bye('guys'); ok($hellostr eq 'Hello, guys!', 'hello() works'); ok($byestr eq 'Goodbye, guys!', 'bye() works'); my $helloworld=Hello::hello(); ok($hellostr eq 'Hello, world!', 'should be hello, world! by default');
需要特别说明的是,在写测试脚本之前,必须事先声明计划执行的 testcase 的个数,如:
use Test::Simple tests => 3;
我们在命令行中执行 perl test_simple.perl,观察程序的输出如下:
清单 3. Test::Simple 示例代码执行结果
C:\MySpace\workdir>perl test_simple.perl 1..3 ok 1 - hello() works ok 2 - bye() works Use of uninitialized value $you in concatenation (.) or string at Hello.pm line 9. not ok 3 - should be hello, world! by default # Failed test 'should be hello, world! by default' # at hello.t line 12. # Looks like you failed 1 test of 3.
从测试结果中不难看出,第一个和第二个 testcase 成功通过测试,而第三个 testcase 则失败了。
Test::More 的介绍
从字面意思上不难看出 Test::More 比 Test::Simple 提供了更多更广泛的对 testcase 是否成功的支持。下面简单介绍其中的一些常用功能。和 Test::Simple 一样,Test::More 同样需要事先申明需要测试的 testcase 的个数。比如:
use Test::More tests => 10;
然而,你可能在最初并不能预见到底需要测试多少个 testcase。为此 Test::More 提供了另外一种在最下方用 done_testing 的方式来达到这个目的。对应的代码如下:
use Test::More; …… run your tests …… done_testing($number_of_tests_run);
这时,你甚至可以用 skip_all 来跳过 testcase。
use Test::More skip_all => $skip_reason;
Test::More 中提供了许多使用的方法,表 1 中列举出了其中的一些。
表 1. 常用 Test::More 方法
方法 | 说明 | 用法 |
---|---|---|
ok | 判断 testcase ok | ok($got op $expected,$test_name); |
is/isnt | 字符串比较 | is($got,$expected,$test_name); |
isnt($got,$expected,$test_name); | ||
like/unlike | 正则表达式比较 | like( $got, qr/expected/, $test_name ); |
nlike( $got, qr/expected/, $test_name ); | ||
cmp_ok | 可以指定操作符地比较 | cmp_ok($got,$op,$expected,$test_name); |
can_ok | 被测模块或对象的方法 | can_ok($module,@methods) |
can_ok($object,@methods) | ||
isa_ok | 对象是否被定义或对象的实例变量确实是已定义的引用 | isa_ok($object,$class,$object_name); |
isa_ok($subclass,$class,$object_name); | ||
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,$expected,$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, Arg2, Arg3 )
Arg2 是一个正则表达式 , 比较 Arg1 是否 匹配 Arg2 正则表达式。Arg3 是指 testcase 的名字。
cmp_ok( Arg1, Arg2, Arg3, Arg4 );
cmp_ok() 允许用任何二元操作符(Arg2)比较 Arg1,Arg3. 同样,Arg4 是指 testcase 的名字。另外 cmp_ok() 有个好处,如果 testcase failed, 结果中会报告 Arg1 和 Arg2 在运行中的实际值。
can_ok($module, @methods); can_ok($object, @methods);
can_ok() 判断模块 $module 或对象 $object 能否调用方法 @methods。
更多方法可以参考 CPAN 上关于 Test::More 的更多的介绍。
基于这些函数,我们能非常方便的设计和实现基于 Test::More 的 testcase。示例代码 test_more.perl 如下:
清单 4. Test::More 示例代码
use strict; use warnings; use Test::More tests => 3; use Hello; # What you're testing. my $hellostr=Hello::hello('guys'); my $byestr=Hello::bye('guys'); is($hellostr, 'Hello, guys!', 'hello() works'); like($byestr, "/Goodbye/", 'bye() works'); cmp_ok($hellostr, 'eq', 'Hello, guys!', 'bye() works'); can_ok('Hello', qw(hello bye));
在命令行下运行 perl test_more.perl 后,我们可以看到程序的输出如下:
清单 5. Test::More 示例代码执行结果
C:\Perl\test> perl more.perl 1..3 ok 1 - hello() works ok 2 - bye() works ok 3 - bye() works ok 4 - Hello->can(...) # Looks like you planned 3 tests but ran 4. cmp_ok($hellostr, 'eq', 'Hello, guys!', 'bye() works'); can_ok('Hello', qw(hello bye));
Test::Class 的介绍
定义一个测试类,只需要编写一个从 Test::Class 继承的子类,申明如下:use base qw(Test::Class);
由于 Test::Class 本身没有提供测试函数,而是使用 Test::More 之类的其他测试框架的方法,因此需要申明 Test::More 模块 :
use Test::More;
Test::Class 的常用方法包括:
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 方法
setup 和 teardown 分别在每个普通测试方法之前和之后调用。
Setup 用法
sub method_name:Test(setup) \{...};
sub method_name:Test(setup=>N)\{...};
teardown 用法
sub method_name : Test(teardown) \{ ... };
sub method_name : Test(teardown => N) \{ ... };
startup 和 shutdown 方法
startup 和 shutdown 方法用法和 Setup,teardown 方法类似,区别在与 Startup 和 shutdown 是在所有测试方法执行之前和之后调用。
Runttests 方法
通过调用 runtests() 方法,执行所有从 Test::Class 派生的子类中定义的测试 case,用法:
Test::Class->runtests();
下面的例子是基于 Test::Class 的测试代码 test_class.perl。
清单 6. Test::Class 示例代码
use strict; use warnings; use Hello; # What you're testing. use Test::More; use base qw(Test::Class); my $hellostr=Hello::hello('guys'); my $byestr=Hello::bye('guys'); sub initial : Test(setup) { print "Begin One Test...\n"; } sub end : Test(teardown) { print "End One Test...\n"; } sub test_hello : Test(1) { is($hellostr, 'Hello, guys!', 'hello() works'); } sub test_bye : Test(1) { like($byestr, "/Goodbye/", 'bye() works'); } Test::Class->runtests();
在命令行中执行 perl test_class.perl 后的测试结果如下:
清单 7. Test::Class 示例代码执行结果
C:\Perl\test> perl test_class.perl Begin One Test... 1..2 ok 1 - bye() works End One Test... Begin One Test... ok 2 - hello() works End One Test...
回页首
应用实例
有了之前对于几个常用 Perl 单元测试框架的介绍,下面我们给出一个具体的实例,来测试一个 CPAN 中的一个 module File::Util。部分单元测试的代码如下。清单 8. 应用实例代码
#!/usr/bin/perl -w use strict; use File::Util; use Test::More; use base qw(Test::Class); my $file; sub init : Test(startup){ print "####################################################\n"; print "This script is used to test some subs in File::Util.\n"; $file = File::Util->new(); } sub shutdown : Test(shutdown){ print "Finished all testcases.\n"; print "####################################################\n"; } sub initial : Test(setup) { print "----------------------------------------------------\n"; print "Begin One Test...\n"; } sub end : Test(teardown) { print "End One Test...\n"; print "----------------------------------------------------\n"; } sub test_methods : Test(1) { can_ok('File::Util', qw(existent line_count list_dir)); } sub test_existent_true : Test(1) { open(FILE, ">test.txt"); print FILE "This is a test."; close FILE; cmp_ok($file -> existent('test.txt'), "==" ,1, 'test_existent_file_exists'); unlink "test.txt"; is($file -> existent('test.txt'), undef, 'test_existent_file_not_exists'); } sub test_line_count : Test(1) { open(FILE, ">test.txt"); print FILE "This is a test.\n"; close FILE; cmp_ok($file -> line_count('test.txt'), "==" ,1, 'test_line_count = 1'); open(FILE, ">test.txt"); print FILE ""; close FILE; cmp_ok($file -> line_count('test.txt'), "==" ,0, 'test_line_count = 0'); } Test::Class->runtests();
程序中设计了三个 testcase,分别用于测试模块中的方法的命名空间可见性、existent 方法和 line_count 方法。测试的结果如下:
清单 9. 应用实例代码执行结果
#################################################### This script is used to test some subs in File::Util. ---------------------------------------------------- Begin One Test... 1..3 ok 1 - test_existent_file_exists ok 2 - test_existent_file_not_exists # expected 1 test(s) in main::test_existent_true, 2 completed End One Test... ---------------------------------------------------- ---------------------------------------------------- Begin One Test... ok 3 - test_line_count = 1 ok 4 - test_line_count = 0 # expected 1 test(s) in main::test_line_count, 2 completed End One Test... ---------------------------------------------------- ---------------------------------------------------- Begin One Test... ok 5 - File::Util->can(...) End One Test... ---------------------------------------------------- Finished all testcases. #################################################### # Looks like you planned 3 tests but ran 5.
相关文章推荐
- Perl 脚本中单元测试自动化浅析
- Perl 脚本中单元测试自动化浅析
- 自动过滤邮箱日志,并发送邮件给邮箱脚本,perl+shell
- 查看linux服务器硬件信息的perl脚本
- Linux的第一个perl脚本
- Perl脚本 自动获取Chrome最新版本
- perl脚本对execl的操作
- ubuntu下perl脚本
- Perl 脚本执行错误/usr/bin/perl^M: bad interpreter: No such file or directory
- 有人这么比较:ASP,JSP,JSP,Python,Ruby,PERL 等网页脚本语言的比较
- 用Perl、Shell脚本基于Tomcat6开发cgi程序环境配置
- 用Perl脚本实现MS批量任务
- Apache Web 服务器配置CGI程序,执行Python、Perl脚本
- perl脚本远程连接mysql数据库
- 使用 Perl 脚本实现交互式命令行程序的管理与测试自动化
- 脚本语言性能比较:Ruby,Io,PHP,Python,Lua,Java,Perl...
- Android---ASE---脚本语言研究1--perl
- perl脚本备份还原sqlserver
- Perl 脚本自动杀死指定process 所有进程
- Perl 脚本简单应用实例-MySQL备份