在 IT 产品系统测试的自动化项目中,经常有并行处理多个子任务的需求,为了提高测试效率,就需要用到多进程或者多线程编程。文章介绍了
Perl 多进程的用法和适用场景,以及 Perl 多进程和多线程的关系。同时结合企业级 IT 产品系统测试的自动化特点,介绍了
Perl 多进程在这方面的应用。
Perl 的多进程
Perl 语言是一种非常强大的脚本语言,其广泛应用于系统维护,CGI(Common Gateway Interface)编程,数据库编程和自动化测试中。
多任务和并发处理一度被看作是判定优异操作系统的一个特性;同样任何优秀从而流行的编程语言都会有并发的应用,而且都有各自的实现方法。Perl
最开始在并发方面的应用就是多进程。
Perl 多进程的用户接口是 fork() 函数以及对系统 fork 函数封装的一些 module。我们在使用
Perl 语言编程时,如果一个任务的某一个段可以或者需要并发很多执行,那么我们就会使用 Perl 的多进程编程,例如同时向数据库递交多个记录的查询,同时完成多个系统信息的收集等等。
Perl 的多进程是这样实现的:主进程每 fork 一个子进程,会把当前(主进程的)内存空间的所有变量都复制一份传到新的进程里面,达到数据共享的目的。此外,主进程和子进程可以通过信号、管道等来通信。
在处理并发的方案中,多进程依靠内存空间独享提供了优秀的容错性和健壮性。一个多进程的系统不会由于其中一个进程的状态不良而崩溃,每个进程都相对独立地运作,很少会相互影响。
内存空间独享也造就了多进程不可避免的劣势:资源负荷以及通信复杂,对于 Perl 来说,每个子进程都可以看作主进程的拷贝,这多少有些内存浪费,而且主进程的关键变量如果是“浅复制”到子进程的话,将会带来一些意想不到的错误。另外,进程的创建和回收会带来许多额外的负载,因此应当尽量避免频繁地创建进程。
多进程之间的通信方式有 socket,管道,信号量等。在 Linux 平台上,对于进程间大量信息的交互情况,最常用的是文件;这在用户空间进程和系统内核空间进程之间的通信中的最为常用。
在谈起 Perl 多进程的时候,不可避免的要说说它与 Perl 多线程的关系。
Perl 的多线程和多线程的关系
如果我们需要执行一些各异的小任务,他们可能数量较多,但生存周期都比较短,或者他们可能共享大量数据,只有小部分的变量是自身的。这个时候,很自然就想到了多线程。
从 Perl 5.005 开始,多线程的概念被引入 Perl 语言了;不过仅仅是概念而已。在 Perl
5.005 中,线程模型默认共享所有的数据,而且数据的共享访问需要明确的同步操作。这个模型被称为“5005threads”,其实就是多进程,只不过换了个“多线程”的概念。
到了 Perl5.6 ,一个新的线程模型引入了,各个线程的数据默认是私有的,而且共享数据的访问需要显式地调用相关的函数来保证同步。这个模型称为“ithreads”。在
Perl 5.6 中, ithreads 模型并没有提供一些 API 供用户调用,仅作为一个内部的 API
供扩展使用。
到了 Perl 5.8,ithreads 模型通过 Perl 的 threads 模块供用户使用,但是
Perl 5.8 同时也保留了 5005threads 模型。这里,用户可以通过配置来选择使用哪一种模型。
对于最新的 Perl 5.10, 5005threads 模型就不再使用了,已从 Perl 解释器中移除了。所以要使用真正意义的、稳定的多线程模块,建议使用
Perl 5.10 解释器。2009 年 3 月 release 的 SLES11 的安装包已经默认使用
Perl 5.10 了。至此,多进程和多线程应该是区别开来了。
Perl 的 threads 模块提供的多线程,应用于我们经典的多线程编程例如 Socket Server
编程、硬件驱动或者适配层编程十分有效,然而对于非线程安全的一些函数或者模块调用就无能为力了,会导致不可预知的错误或者主进程的吊死、崩溃。
很多大型 IT 公司都以版本控制来发布一系列的企业级系统产品,为此,周期性地更新产品的每个新版本和已有产品版本间的兼容性和互操作性测试结果,对客户而言,尤为重要。而这类测试由于重复性强,工作量大,所以经常采用自动化。下面举一个自动化测试中的常见场景例子。
假设有一个团队从事企业级产品系统之间的兼容性和互操作性测试,需要在以 1 个月为周期的时间内完成一组配置的兼容性测试,团队成员很少,但是负责的设备却很多,往往每个人需要负责数十台甚至数百台机器,那么就必须采用多任务并发处理的自动化测试。该团队测试存储网络中的服务器,交换机和存储之间的互操作性。服务器包括很多个硬件平台,操作系统包括
Linux,Unix,Windows 和 VMware 等,光纤交换机、主机总线适配器卡和存储产品都涵盖多个厂商品牌。这个团队就需要建立一个端到端的自动化测试流程来支撑其测试任务。
下面是一个典型测试环境的简化图示。
图 1. 测试硬件环境概览图
其中,服务器有不同 CPU 架构的众多品牌;它们上面运行的测试操作系统有 Linux、Windows、VMware
和各类虚拟机。物理层的光纤交换机(FC Physical Layer Switch)是测试工具仪器,用于控制光纤的链路通断等异常测试。
下面是自动化测试环境框图。
图 2. 自动化测试环境框图
用一台运行 Red Hat Enterprise Linux 系统的 x Server 作为 Test
console,上面安装 IBM Rational Build Forge 用于测试的执行和管理。Test
console 通过以太网络与各个 server,switch,physical-layer switch
和 storage 进行通信,包括测试脚本的传送,测试命令的信息交互。
这里说明一个测试用例,从而讲述一个典型测试过程。“主机和光纤交换机之间的光纤断开测试”:
- Test console 发命令到所有的主机,让每个主机发现存储磁盘,分区,格式化文件系统,mount
或者分 windows 盘符,配置 IO 程序等;然后检查各个被测应用程序以及系统的运行状态,把结果返回
Test console;
- Test console 发命令到所有的光纤交换机,让每个光纤交换机检查自身当前的运行状态,然后返回结果到
Test console;
- Test console 发命令到所有的存储,让每个存储机器检查自身当前的运行状态,然后返回结果到
Test console;
- Test console 在保证所有主机,交换机和存储设备都进入要求的测试状态后,开始进入测试,否则退出;
- Test console 发命令让每个 physical-layer switch 开始进行相关的光纤连接的断开,然后等待一定的时间,再合上;
- Test console 发命令到所有的主机,检测它们的状态,等待所有的主机都恢复正常;
- 重复 5、6 两步,测试持续 24 小时,完成。
可见,在这样的自动化测试过程中,很多次需要 Test console 和几十个或者几百个机器进行命令交互,检查状态或者执行任务。这些操作当然可以是一个系统接着一个系统的执行;但是大多数任务是可以并发的,或者是可以同时进行从而提高测试效率的,比如同时访问多个主机去启动
IO 程序、检查机器状态,在登录存储上执行一个脚本的同时在多个主机上面执行一些应用程序,同时登录所有的光纤交换机执行一些配置命令等。
另外,Test console 和被测系统(Server、Switch、Storage 等)之间的命令交互操作,需要使用
Perl Expect 模块,或其它基于 Expect 的模块例如 Perl::SSH::Expect,
Perl::Telnet::Expect 等。很可惜,Expect 模块并不是线程安全的。
显然,这就需要应用 Perl 的多进程技术到自动化测试中。
在自动化测试中,利用 Rational Build Forge 作为测试管理和监控工具,90% 以上的测试脚本都采用
Perl 脚本。采用了模块化编程,并大量使用 CPAN 提供的 module。由于很多 module 都不是线程安全的,同时为了提高脚本开发效率,也会在一个脚本中直接调用另外一个脚本,所以选择在自动化测试框架中应用
Perl 多进程。
自动化测试脚本使用多进程大致分为两种情况。一种是 Test console 需要同时操作十几个设备,例如用于主机和存储互连的光纤交换机,用于光纤物理层断开测试的交换机
APCON 等。这类测试的特点是需要同时操作 10 几个或者 20 几个的测试对象,对它们的操作是配置操作,而且配置脚本都会成功,主进程只需要所有子进程执行完毕就认为所有子任务已经完成,然后可以进行后续的操作。因此采用一种较为简单的多进程编程方式。下面是针对这种情况的多进程处理的代码示例。
清单 1. 多进程处理的代码
our @cmd = ("./apcon_2052.exp 1 1 9.11.217.27 A15B15",
"./apcon_2058.exp 1 1 9.11.217.65 admin teamw0rk A05A06",
"./apcon_2052.exp 1 1 9.11.217.27 B09D09");
our $zombies = 0;
our $kid_proc_num = 0;
$SIG{CHLD} = sub { $zombies++ };
for(my $i=0; $i<@cmd; $i++) {
my $pid = fork();
if( !defined($pid) ) { exit 1; }
unless($pid) {
system "$cmd[$i]";
exit 0;
}
$kid_proc_num++;
}
while (1) {
if($zombies > 0) {
$zombies = 0;
my $collect;
while(($collect = waitpid(-1, WNOHANG)) > 0) {
$kid_proc_num--;
}
}
if($kid_proc_num==0) { last; }
else { next; }
} |
以上的代码采用多进程方式同时处理了对 3 个测试设备的配置操作,然后主进程等待所有配置操作完成,再进行后续的测试。
另外一种情况较为复杂,就是经常需要针对数百个主机和存储进行配置、状态查询等,而且配置结果和查询状态需要返回主进程处理,然后根据结果数据再决定如何继续。对于这种情况,主进程需要生成数百个子进程,而且每个子进程都有大量的信息返回给主进程来处理。这样处理数百个子进程的生成:由于多进程方式占用系统资源较多,因此设定一个允许主进程同时运行的最多子进程数目,然后在有子进程结束时,主进程再生成新的子进程至所有的子任务完成。这样处理主进程和子进程的通信和信息交互:由于测试
Perl 脚本运行平台为 Linux,它是带有 BSD 风格的 POSIX 兼容的系统,会提供可靠的信号,所以仍然使用系统提供信号的来获取子进程结束的消息;对于子进程的返回信息,采用为每个子进程产生一个临时文件用于存储返回的所有信息,最后主进程来处理这些文件从而获取每个子任务的结果信息。如下是为这种情况设计的多进程处理方式的软件流图。
图 3. 软件流图
下面以登录每个主机检查运行状态的例子给出多进程处理数百个子任务的代码。
清单 2. 多进程处理数百个子任务
use strict;
use warnings;
use POSIX ":sys_wait_h";
our @IP = &getAllHostIP();
our $TOTAL_TASK = @IP; # assume we have 300 hosts
our $MAX_LIVE_PROC = 24; # Maximum number of subProcess allowed to co-exist
our @result_filename = ();
our $zombies = 0;
$SIG{CHLD} = sub { $zombies++ }; # handle message from kernel
our $handled_task = 1;
our $cur_live_proc = 1;
my $pid = fork();
unless ($pid) {
my $filename = &getUniqueFilename();
push @result_filename, $filename;
system "./checkHost $IP[$handled_task-1] $uid $psw $filename";
exit 0;
}
while ($cur_live_proc>0) {
if ($zombies>0) {
$zombies=0;
my $collect = 0;
while(($collect = waitpid(-1, WNOHANG)) > 0) {
$cur_live_proc--;
}
}
if ( ($cur_live_proc<$MAX_LIVE_PROC) and ($handled_task<$TOTAL_TASK) ) {
$handled_task++;
$cur_live_proc++;
my $pid = fork();
unless ($pid) {
my $filename = &getUniqueFilename();
push @result_filename, $filename;
system "./checkHost $IP[$handled_task-1] $uid $psw $filename";
exit 0;
}
} else {
next;
}
}
&checkResults(); |
根据常见的设备系统测试自动化平台的特点和要求,把 Perl 的多进程技术应用到了测试脚本中,极大地提高了测试效率。
Perl 多进程已经很长的发展历史,而且应用广泛,技术成熟。多进程在健壮性和容错性方面表现更好,每个进程都拥有独立的内存空间,并行的几个进程一般来说不会相互干扰;当然,相应的,多进程的系统开销也比较大,而且进程间通信也变得复杂一些。妥善地处理多进程生成和进程间的通信,会很好地改善自动化测试的运行效率以及稳定性。
|