开篇:润墨网以专业的文秘视角,为您筛选了一篇接口变异在构件测试中的应用研究范文,如需获取更多写作素材,在线客服老师一对一协助。欢迎您的阅读与分享!
摘要:对于构件使用者来说,源代码的不可得性决定了测试构件需要从构件的接口层面上进行。文章首先介绍了变异测试和遗传算法的概念;接着将遗传算法运用到构件接口变异上,通过变异测试过程获取有效而充分的测试用例,进而对构件进行测试;最后通过一个实例验证这种测试策略。
关键词:构件测试;接口变异;遗传算法
中图分类号:TP311文献标识码:A文章编号:1009-3044(2010)13-3561-03
Application Research of Interface Mutation in Component Test
LIU Chang-you, GUAN-Xian-chun
(Computer School of GuangDong University of Technology, Guangzhou 510006, China)
Abstract: Source code of component is unavailable to component users, so component test has to been run on component interface. Firstly, This paper introduced Mutation Testing and Genetic Algorithms; Secondly, applying Genetic Algorithms to interface mutation, and by that to create adequate and effective test cases which are used to test component; At last, an example is provided to validate the test method.
Key words: component testing; interface mutation; Genetic Algorithms
近年来,基于构件的软件开发(CBSD)逐渐发展起来,且越来越受到业界的欢迎。与传统的软件开发过程不同,CBSD开发一个系统可以由现成的构件组装而成,而不必一切都从头开发。这一变化大大提高了软件开发的效率。由于CSBD取得成功的前提是已经开发出了质量可靠的构件,因此如何有效地对构件进行测试成为软件工程领域研究的热点。
对构件使用者来说,构件具有一个最显著的特征[1],即构件的源代码的不可得性,只有通过调用才能使用构件的功能。由于源代码的不可得使构件验收和部署时的测试工作变得困难,因此构件的测试必须从更高的层次入手,即从构件提供的接口进行测试。通过接口调用和参数传递观察构件的外在行为,用以验证构件是否符合规格说明。因此如何设计出有效而充分的测试用例成为基于构件接口测试的关键任务。本文正是根据这种思路把遗传算法应用到构件的接口变异中去,通过变异测试生成有效而充分的测试用例,最后运用这些用例测试构件的功能。
1 构件与变异测试
1.1 构件与构件测试的现状
根据《计算机百科全书》中的定义[2],构件是软件系统中具有相对独立功能、可以明确辨识、接口由契约指定、和语境有明显依赖关系、可独立部署、可组装的软件实体。而构件的质量关系着构件化软件开发的成败,因此在进行构件化软件开发前要对构件进行充分的测试。
目前构件测试方法有内置测试法、元数据法、构件交互图,基于合约的接口变异测试方法等。根据构件测试人员的不同可以分为构件开发者的测试和构件使用者的测试。这里主要研究接口变异的测试方法,即站在构件使用者的角度一种测试方法。该方法允许构件测试者根据构件的规格描述信息生成初步的测试用例,通过遗传算法对构件的接口进行变异,把构件测试用例应用到变异后的构件接口上,经过一系列迭代的测试过程逐步完善测试用例,直至测试用例的有效度达到要求为止,最后运行测试用例观察构件的交互是否满足规格需求。
1.2 变异测试的过程
基于变异的软件测试是一种有效的软件测试方法。它需要执行一个程序的多个不同版本用来评价所使用的测试用例。这里要对为测试的程序构造出变异来,一个变异体就是引入了一个简单的语法错误到程序中。整个变异测试过程(图1)可以描述如下:
被测试的程序P,它的变异体为Pi(i=1,2,3,…N)。在测试集T上执行变异子Pi,与执行P的结果比较。
如果 Pi(T)P(T) 那么Pi被杀死。
如果 Pi(T)==P(T) 这里有两种情况,一是Pi是P的等价变异体;另一种情况是用例不够完善尚且不能揭示出Pi里面存在的错误。
这里介绍下变异充分度参数MS定义:
■
其中D是被杀死的变异体个数。
N是所有变异体个数。
E是等价变异体的个数。
理想的情况下MS为100%,即测试用例T能杀死所有的非等价变异体。实际上往往难以达到100%的充分度,因为等价变异体的判定在有些情况下是非常困难的。一般来说在达到可以接受的充分度后就结束测试。
通过上述的变异过程可见,变异测试的目的是生成有效的测试用例,目前应用较广的是运用遗传算法生成变异测试用例。
2 遗传算法
2.1 遗传算法的概念
遗传算法(Genetic Algorithms)[3]是从代表问题可能潜在的解集的一个种群开始的,而一个种群则由经过基因编码的一定数目的个体组成。每个个体实际上是染色体带有特征的实体。染色体作为遗传物质的主要载体,即多个基因的集合,其内部表现(即基因型)是某种基因组合,它决定了个体的形状的外部表现。因此,在一开始需要实现从表现型到基因型的映射即编码工作。由于仿照基因编码的工作很复杂,我们往往进行简化,如二进制编码。初始代种群产生之后,按照适者生存和优胜劣汰的原理,逐代演化产生出越来越好的近似解,在每一代,根据问题域中个体的适应度大小选择个体,并借助于自然遗传学的遗传算子进行组合、交叉和变异,产生出代表新的解集的种群。
在软件测试过程中,一个基本的观点是要确定满足测试目标的输入变量域。这个问题就是最大化一个函数f(x1,x2,…xm)。在遗传算法中,每个染色体代表一组测试用例。遗传算法着眼于原程序输入域,其中合适的测试用例可以杀死原程序的变异体。遗传算法的个体是二进制或字母的串,需要为每个个体定义一个适应度函数f(x),适应度函数的值就是指测试用例发现程序错误的能力和效率。
2.2 遗传算法的操作符
遗传算法使用以下操作符[4]:选择(复制)、交叉和变异。
1)选择(复制)
选择就是从一代中选择两个个体进行交叉、变异等重组的过程。有不同的选择个体的方法,比如随机法或者根据它们的适应度函数的值。
2)交叉
交叉操作符作用在个体水平上,在交叉操作前要把个体转化为一个二进制表达形式,即编码的过程。交叉过程中双亲在个体的随机位置交换子串的信息生成新的串,即子代。这个过程的目的就是通过组合双亲的信息创造出更好的个体来。
3)变异
变异操作符修改个体的一个或多个基因值。假如个体是一个二进制串的话,变异就意味着把其中的0变为1。
2.3 遗传算法的应用过程
有了以上三个操作符与适应度函数,那么遗传算法的应用过程可以描述为:
1)创建初始种群。2)为每个种群的个体求它的评估适应度函数f(x)的值。3)使用复制、交叉和变异操作符创建新种群。4)用新种群取代旧种群作为新一代。5)测试问题规范。6)返回第2步。
使用遗传算法我们可以计算出更好的测试用例,可以改善使用大量测试用例运行变异体的困难。因此遗传算法改进了有效测试用例的生成过程,从而减少对变异程序执行测试用例的时间。
3 运用遗传算法生成接口变异
3.1 接口变异概述
接口测试是从构件使用者的角度出发,其目的是评价构件是否能够准确有效地进行交互。它不像传统的变异把变异操作符应用到整个程序中,而是变异构件接口的参数。参数包括函数参数,返回值或全局变量。下面给出了常用的面向对象的变异操作符,如表1。
3.2 遗传算法应用到接口变异过程
在Java RMI中,使用Java来定义接口,而在CORBA和DCOM中使用IDL(接口定义语言)。本文以更具通用性的语言即IDL来描述接口变异。在IDL中[5],一个参数如果是输出参数,可以被声明为out;如果是输入参数,可以定义为in;如果它包含了输入及输出值,则可被声明为inout 。根据遗传算法对接口进行变异的一些策略:
1)参数替换操作符,即in, out, inout中一个被其它某个替换。例如:
原始IDL:Test(in vType v, in xType x)
变异IDL:Test(out vType v, inout xType x)
2)方法调用中的变量交换操作,相同类型的变量可以在方法调用中交换。例如:
原始 IDL:Test(in vType v1, in vType v2)
变异 IDL:Test(in vType v2, in vType v1)
3)参数递增或递减运算符作用在接口传递的变量上,并替换变量x为x+或x-,变量x只是被作为一个参数传递。例如:
原始IDL:Test(in vType var)
变异IDL:Test(in vType var+)
4)把一个固定值赋给参数,例如:
原始IDL:Test(in vType var)
变异IDL:Test(in vType const)
5)使一个对象引用为空。例如:
原始IDL:Test(in vType var)
变异IDL:Test(Null)
6)插入绝对值符号。例如:
原始IDL:Test(vType var)
变异IDL:Test(vType |var|)
3.3 接口变异测试的充分度评价
在IDL中,接口描述不包含任何代码和不被实现的信息,所以变异操作符必须应用到变异实现上去。构件接口描述信息包含了构件内部的方法,签名以及可能会产生的异常。通过分析这些信息,测试人员可以获取一些覆盖域。以下是两个评价接口变异测试充分度准则[6]:
充分性准则AC1:如果方法覆盖率达到100%,说明在测试过程中被测构件的所有方法都被执行了。
充分性准则AC2:如果异常覆盖率达到100%,说明在测试过程中所有定义的异常都被抛出了。
但是方法和异常覆盖准则是繁琐而且不充分的。因为构件内部代码执行将会访问和修改传递到方法和异常的参数。代码根据参数值和类型的不同会有多个执行路径,执行一次就覆盖了一个方法或异常并不能说明所有的路径都会被覆盖。构件里的对象有它们的状态,有必要追踪对象的状态。还要通过不同参数的值来执行方法,这可以通过变异测试来完成。
这些覆盖域是在构件接口水平上说的,是在比传统代码覆盖域更高的层次上,覆盖域的大小也因此缩小,这也提高了充分度的度量能力。
3.4 给出的测试流程图
根据以上对接口变异测试过程的描述,可以给出的测试流程图如图2所示。
4 实例研究
下面用一个EJB构件的接口变异测试过程来验证以上所述的测试策略。实例是一个计算器EJB构件Calculator。其中名为Calculator的接口作为Bean的Remote接口,Remote接口必须继承javax.ejb.EJBObject。在Remote接口中声明其中的四个用于计算的业务方法add、sub、mul、div,即加、减、乘、除方法。类CalculatorBean实现一个无状态的会话Bean.,该类必须实现javax.ejb.SessionBean,并实现接口中声明的加、减、乘、除四个运算方法,并有相应的异常处理方法。
下面是对该构件进行变异测试的过程:
1)获取构件需求规格信息,即构件接口提供的方法参数情况,这是进行构件测试的基础信息。
double add(double v1, double v2)
double sub(double v1, double v2)
double mul(double v1, double v2)
double div(double v1, double v2).
2)根据构件规格信息创建初始测试用例T,即两个为一组的实数对,这里要考虑到常用的数据,临界数据,非法数据等等。
3)对构件接口进行变异,生成的变异体示例如下:
double add(double v1, null)
double add(double v1+1, double |v2|)
double add(string s1, double v2)
…
double sub(double v1+1, double v2-1)
…
double mul(double v1, null)
…
double div(double v1, 0)
如此类推,进行接口变异。
4)部署构件测试环境。这里可以使用远程调用的方式,创建一个客户端构件实例调用构件接口提供的各个方法,运行测试用例T在这些变异的接口方法上,把运行结果与期望值进行比较,相同则变异被杀死,不同则另外设计更充分的测试用例,直到测试方法覆盖度和异常覆盖度达到可以接受的限度。(下转第B001页)
(上接第B001页)
5 结束语
构件测试的难点在于如何在源代码不可得的情况下进行测试。文章以构件的接口描述信息为依据生成原始测试用例,然后运用遗传算法生成接口变异,通过变异测试过程获取充分的测试用例。余下的工作就是在测试环境下运用这些生成的测试用例测试构件了。这篇文章的不足是尚没有找出一种有效的自动化方法运用遗传算法变异接口,以后的研究将向这个方面努力。
参考文献:
[1] XiaBin,ponent Configuration Test Based On Mutation[C].IEEE,2008:66.
[2] 杨芙清,梅宏,构件化软件设计与实现[M].北京:清华大学出版社,2008:10.
[3] 王小平,曹立明,遗传算法-理论、应用及软件实现[M].西安:西安交通大学出版社,2002:7.
[4] Md Masud M,Nayak A.A Strategy for Mutation Testing Using Genetic Algorithms [J].IEEE,2005.
[5] Liu M L.分布式计算原理与应用[M].顾铁成,王亚丽,叶保留,译.北京:清华大学出版社,2004:251.
[6] Ghosh S,Mathur A P.Interface Mutation to Access theAdequacy of Tests for Components and System[C].IEEE,2000.