张 鑫 刘冬梅 朱 鸿陈 颖 兰 斌 孙玉虎
(1.南京理工大学计算机科学与工程学院 南京 210094)(2.英国Oxford Brookes大学计算与通讯系 牛津 OX33 1HX)
随着Web服务技术[1]的迅速发展和日益广泛的应用,对Web服务的测试已成为Web服务研究中的热点,尤其是在服务动态发现、选择和使用时,测试是检验第三方提供的Web服务功能是否正确可靠的一个关键技术,是保证对第三方提供的Web服务的使用方式正确性的有力途径,是建立服务消费者和提供者之间信任的客观依据[2]。但是,当服务在运行时刻动态地发现、选择和调用第三方Web服务时,要求完全自动地完成全部测试过程,这对软件测试技术提出了更高的要求。而另一方面,服务系统的独立性和自主性使得测试者缺乏对第三方服务运行时的控制以及有效的观察手段,对Web服务进行完全自动化的测试更加困难[3]。其中,如何完全自动地对动态发现的Web服务执行测试用例是Web服务软件自动测试技术尚未解决的问题。
测试执行是Web服务测试的重要组成部分,是实现Web服务测试自动化的必备条件。现有Web服务具有不同的服务接口标准,较常见的有Big Web服务和RESTful Web服务,前者以WSDL、SOAP、UDDI、OWL-S等为标准,而后者以 HTTP2的REST体系构架为基础。它们之间没有统一的接口标准,也没有通用的服务调用机制。因此,如何实现服务的测试执行自动化仍是一个待解决的问题。
本文提出一个面向Web服务的测试执行引擎来完成自动测试执行,在分析主流Web服务接口的基础上,定义一个通用的服务调用机制,该机制在分析测试脚本后自动调用Web服务,然后将获取的执行结果保存在本地。
本文的研究内容是基于代数规约的Web服务自动测试。近十年来,Web服务测试是面向服务计算领域一个十分活跃的研究课题,已有大量的研究成果[4~13],而基于代数规约的测试技术已成功应用于面向过程、面向对象、基于构件的软件系统。因此,本章分两个方面简要讨论相关工作。
近年来,Web服务测试研究发展迅速,已从单个服务的静态功能测试发展到服务组合的动态运行能力测试。
早期的Web服务测试方法主要使用WSDL文档中的XML Schema数据类型信息,基于操作或者消息等覆盖率准则为每个服务操作生成测试数据,进行相应的边界值分析、随机测试或者等价类测试[4~6]。但是,WSDL文档仅仅包含输入数据类型等语法信息,缺少描述服务行为的语义信息,影响了该类方法的有效性。
针对上述问题,研究人员提出扩展WSDL来描述输入输出依赖关系和服务操作调用等语义信息[7~8],进而开展基于语义描述的 Web 服务测试研究。WSDL-S、OWL-S等主流语义Web服务描述以及用BPEL对Web服务组合的描述不仅提供了服务操作中数据类型的语义信息,而且提供了服务的行为信息,这使得Web服务的可测试性增强。但是,以本体为基础的语义描述不能准确地定义服务的功能。
总之,基于语法描述和语义描述的测试方法主要根据服务提供商发布的WSDL、OWL-S、WSMO、BPEL等服务描述文档进行测试。但是,由于这些服务描述文档缺乏对服务功能的准确规约,服务功能的正确性不能得到充分和有效的测试。一个有效的解决途径是基于形式化规约的测试方法[9]。
应用形式化规约方法描述Web服务能够提供一种可测试和可验证的服务规约[10]。大部分已有研究工作[11~13]主要将WSDL-S、OWL-S等服务描述转换成形式化规约后再进行测试,但是将服务转换成形式化规约需要人工参与或者额外的服务语义描述,难以适应Web服务自动化测试。直接应用于Web 服务测试的形式化规约[14~16]主要是基于行为的规约,主要包括扩展有限状态机、标签转换系统和扩展Petri网等。这类技术侧重于测试服务的动态行为是否有效,但忽略了服务功能的正确性。
代数规约可以在较高的抽象层次上定义软件的功能,能够在不依赖软件实现细节的前提下描述系统的可观察行为。实验数据表明,代数规约比其他形式化方法更易学易用[17]。近年来的研究表明,从服务的代数规约自动生成应用领域的本体定义以及服务的本体语义描述[18],可以建立形式化规约和本体语义描述的联系。代数规约的这些特性对于Web服务来说尤其重要,因为绝大部分服务使用者无法得到服务实现细节,并且对于动态组合的服务来说,以本体为基础的语义描述是服务发现和动态组合的基础。因此,代数规约很合适于在缺乏服务实现细节的情况下对Web服务进行抽象描述,不仅能够描述服务功能,而且能够描述服务的动态行为。Zhu等提出一个代数规约语言CASOCC的扩展方案 CASOCC-WS[19],以便书写 Web服务的形式化规约。在大量案例研究的基础上,Liu等完善面向服务软件的代数规约语言,提出了SOFIA语言[20~21]。
另一方面,基于代数规约的测试支持完全自动化的测试过程[3],包括测试用例生成、测试平台生成和测试结果的正确性检验,这对于Web服务自动化测试来说至关重要。基于代数规约的测试方法目前已经应用于过程语言表示的抽象数据类型[22]、面向对象程序[23]以及基于构件程序的测试[24],并开发了 DASTS[22]、ASTOOT[23]和 CASCAT[24]等试验性工具。而在基于代数规约的测试理论和技术研究方面,通过引进渐增式集成测试策略,解决了测试组合大爆炸问题。但是,已有基于代数规约的自动化测试技术均依赖于对被测对象进行创建、初始化和复制等操作来设置测试环境,通过保存和比较被测对象的状态等操作来检验测试结果的正确性,而面向服务的体系结构和Web服务并不支持这些操作。针对上述问题,Liu等提出了基于代数规约的Web服务单体测试方法(Monic Testing)和自动化测试框架[25~26],并针对亚马逊服务AWS、GoGrid等多个实际工业服务进行了案例研究。
本文将以代数规约语言SOFIA和基于代数规约的Web服务单体测试方法为基础,进一步研究测试执行的自动化。
基于代数规约的Web服务单体测试方法的主要思想是用状态增量测试代替传统的固定状态测试、用单线测试序列代替传统的多线测试序列[26]。以Stack服务为例,其主要SOFIA语言规约如下:Spec StackS;
SOFIA语言由若干个规约单元构成,每个规约单元描述一个类子Sort。类子包括基调(Signature)和公理(Axiom),前者定义类子上的操作(Operators),后者定义操作语义。在上述Stack服务规约中,通过Spec关键字命名规约单元;uses语句给出本规约单元中使用的其他规约单元名称;Const语句中定义的nil是堆栈初始化操作;Attr语句中定义的isEm、len和top操作用于判断堆栈是否为空、获取堆栈长度和堆顶元素;Operation语句定义堆栈的push、pop和replace操作;Axiom公理描述了堆栈的特性。
以代数规约为基础的传统测试技术使用基项代替公理中的所有变量,从而生成由两个等价基项构成的测试用例。基项是不含任何自由变量的项。如基项nil.push(1)表示堆栈中只有一个元素1。以下列公理为例:
若以nil.push(1)和数值2分别代替公理ax1中的变量s和x,则对应的测试用例如下:
应用传统的测试技术,该测试用例可以转换成如下两种测试序列:
其中,OC是对堆栈的观察子(Observation Context),例如isEm,len,top,pop.isEm,pop.top,pop.len等等。
从上述两种多线测试序列中可见,基于代数规约的传统测试技术将两个等价项转换成两个对被测对象进行操作的序列,要么对被测对象的两个实例分别进行操作,然后进行状态比较,如序列1;要么对同一实例先后执行两个操作序列,在执行完第一个序列后,记录对象的状态,并对实例进行初始化后再执行第二个操作序列,如序列2。这些技术要么需要生成被测对象的多个实例,要么需要对被测对象进行初始化,从而不适用于Web服务自动化测试。
为解决这一问题,Liu等提出“状态增量测试”和“单线测试序列”两个新技术[26]。状态增量测试技术突破了等价基项作为测试用例的限制,用不依赖于初始状态的等价“自由基项”作为测试用例。自由基项是只包含一个表示被测对象状态的自由变量的基项,它表示对被测对象进行一次状态修改,因此等价自由基项构成的测试用例表示对被测对象的两次修改将获得同样的结果。仍以上述公理ax1为例,其状态增量测试用例如下:
单线测试序列技术将由两个等价项组成的测试用例转换成只包含一个被测对象实例、不包含实例初始化、只对被测对象进行状态修改与状态检查的一个线性序列,该线性序列称为单线测试序列。上述状态增量测试用例t2可转换成如下单线测试序列:
从序列3中可以看出,该单线测试序列只包含一个被测对象实例,不包含实例初始化,并且是一个只对被测对象进行状态修改与状态检查的线性序列。
在上述理论和技术的基础上,Liu等提出了Web服务自动化测试框架[25]。该框架的输入是待测Web服务的代数规约,输出是测试报告,主要包括代数规约解析、测试用例生成、测试执行驱动和测试结果分析四个模块。本文关注测试执行驱动部分(即测试执行引擎),根据输入的单线测试序列调用Web服务执行并获取服务执行结果。单线测试序列是一个线性序列,但序列中可能存在互不影响的操作,将这些操作并行化处理可提高测试执行的效率。因此,本文提出的Web服务测试执行引擎框架首先对单线测试序列进行并行优化处理,然后调用通用的Web服务接口,获取服务执行结果。
本文提出的Web服务测试执行引擎框架包括测试脚本构建和Web服务调用两个模块,如图1。
考虑到单线测试序列的执行操作间可能存在并发执行关联关系,本文首先分析执行操作间的关联性,并将关联关系用矩阵表示,然后基于关系矩阵构造并发流程图,结合服务的代数规约构建测试脚本,最后由测试脚本驱动Web服务调用模块,获取服务执行结果。
图1 Web服务测试执行框架
以Stack服务中的如下公理为例:
其生成的单线测试序列(序列4)包含8个执行操作Si,其中ContinueIf操作表示当条件为真时执行后续的操作,Output操作表示输出测试结果。
4.1.1 关系矩阵构造
执行操作间关系矩阵定义如下:
定义1(执行操作间关系矩阵)由n*n个数mij(i,j=1,2…n)排成的n行n列的数表
其中,行元素Si与列元素Sj表示序列的执行操作,mij表示执行操作Si与Sj间的关联关系。由如下公式确定:
通过分析执行操作间的相关性,上述公理ax2的关系矩阵如表1所示。
表1 执行操作间关系矩阵
如表1所示,当一列有多个1时,表示执行操作间存在并发关系,如第一列中m21和m41值为1表示S2和S4在S1操作后并发执行。当一行有多个1时,表示该行操作在多个列操作执行完毕后执行,如m73和m76值为1表示S7在S3和S6执行完毕后执行。
4.1.2 并发流程图
生成执行操作间的关联关系后,依据以下算法构造并发流程图。
上述算法构造的并发流程图由节点和边组成,节点表示操作,边表示操作执行顺序,其中节点包括开始节点S0(白色圈)、结束节点Sn(黑色圈)、if等式判断节点Sif(菱形)、并发节点Scon(单入度多出度分叉棒)、聚合节点Scol(多入度单出度汇合棒)、操作节点Si(圆角矩形)。公理ax2生成的并发流程图如图2所示:
图2 并发流程图
4.1.3 生成测试脚本
根据并发流程图和待测Web服务代数规约生成测试脚本,本文定义如下测试脚本关键字:
1)服务调用关键字URL,使用语句“URL:url”表示调用服务的地址,该语句也是脚本的开始指令。
2)服务单个操作执行关键字OP,使用语句“OP:操作名|参数”表示执行该操作(多个参数用“,”分隔)。
3)获取结果关键字STORE,使用语句“STORE:结果变量名|获取结果操作”表示将操作结果保存到本地变量中。
4)本地执行操作关键字LOC,使用语句“LOC:变量名|操作名(参数)”表示执行本地操作并保存到本地变量中。
5)条件等式判断关键字JUDGE,使用语句“LOC:JUDGE|判断操作名(参数)”表示判断条件等式是否成立。
6)测试结果生成关键字ASSERT,使用语句“ASSERT:<A,B>”表示变量A与B是否相等,该语句作为脚本的结束指令。
每一条脚本指令表示服务操作或本地操作的执行,当多个操作并发执行时用“&”表示。每条脚本指令以分号结束。
由并发流程图构建测试脚本遵循如下步骤:
步骤1:从服务规约中获取服务的url,构建出脚本的开始指令URL。
步骤2:从并发流程图的开始节点开始,依次执行,如果存在并发的情况,则将多个操作加入同一条指令中。对于每个操作需要判断本地执行还是服务端执行。
步骤3:执行结果判断,即构建脚本结束指令。
因此,公理ax2构建的测试脚本内容如下:
公理ax2的单线测试序列线性执行是依次执行S1到S8操作,其脚本与上述并发测试脚本的区别主要在JUDGE|>(LA,0)和 LB|+(LA,1)操作是顺序执行的,而并发测试脚本中这两个操作可以同时执行。线性执行的脚本指令条数是9条,而优化后的并发测试脚本指令条数是8条。
Web服务调用模块分析测试脚本,调用Web服务获取服务执行结果并生成测试结果。测试脚本解析与服务调用模块可以协同进行,即逐条解析脚本指令,解析完成后立即调用服务。测试脚本分析模块将服务返回的执行结果保存在本地数据池中。
因此,Web服务调用模块执行步骤如下:
步骤1:测试脚本分析器读取脚本的开始指令,启动服务调用选择驱动器寻找Web服务。
步骤2:测试脚本分析器逐条执行脚本指令,并根据需要将服务执行返回结果保存在本地数据池。
步骤3:测试脚本分析器读取结束指令,将测试结果保存。
基于代数规约的Web服务测试要求测试完全自动化,但是,由于Web服务具有不同的服务接口标准,这对于测试完全自动化是一个挑战。本文在分析主流Web服务接口的基础上定义了通用的服务调用机制。
目前大部分Web服务调用方式主要分为两大类:一是具有可视化服务接口的Web服务,二是不具有可视化服务接口的Web服务。前者以网页方式对服务进行访问,本文应用自动化测试工具selenium,通过将浏览器通信托管给selenium的方式,调用可视化服务接口,将执行结果以XML格式返回显示并保存在本地数据池中。后者根据服务实现方式的不同分为基于SOAP方式和基于RESTful方式两类服务,本文通过建立JAX-WS和JAX-RS客户端的方式分别调用SOAP和RESTful Web服务接口。在解析脚本内容后,直接通过url访问服务并获取以XML格式返回的结果。
针对公理ax2,使用开发的测试脚本构建工具获得测试脚本,如图3所示;使用三种服务调用方式测试服务,其结果如图4所示。
图3 测试脚本构建工具
图4 Web服务调用
为了说明基于代数规约的Web服务测试执行技术的可行性和高效性,本文对搭建的Stack服务进行服务调用。服务系统采用如下方式部署:使用MyEclipse将一个包含服务操作的java类分别以基于JAX-WS和JAX-RS的方式发布为SOAP和RESTful服务,而可视化接口方式运用J2EE建立可视化页面,其后台发布为SOAP服务。
Stack服务的规约由19个规约单元组成,包含24条公理,我们选取描述Stack服务主要行为的如下公理进行测试。
实验结果表明,所有服务调用均顺利执行并获取了正确的执行结果,因此,该测试执行技术是可行的。为了进一步证明测试执行技术的可行性,将Stack服务中的push(x)操作的实现改为replace(x)操作,公理1执行的结果显示测试用例通过,但公理2的结果显示等式两边不相等,说明服务的push操作存在问题。
为了研究测试执行技术的效率,对每个公理生成10个测试用例,比较单线测试序列线性执行和优化后执行的时间。每条公理在三种执行方式下测试脚本指令条数的结果统计见表2。
表2 三种执行方式下测试脚本指令条数的结果统计
由表2可知,线性执行脚本指令条数要大于或者等于优化执行脚本指令条数,其中公理3和公理8的指令条数差值为0,原因在于这两个公理的测试序列在执行过程中不存在并发的情况。
三种执行方式在线性执行和优化执行下的平均时间结果统计见表3,其中方式一表示以可视化接口方式调用执行,方式二表示以SOAP方式调用执行,方式三表示以RESTful方式调用执行。
由表3可知,对于测试序列中存在并发执行情况的公理,线性执行平均时间都要比优化执行平均时间长,说明本文提出的Web服务测试执行引擎可以提高测试执行效率。方式一与方式二均为SOAP服务,但方式一调用执行服务的平均时间要比方式二长,这是由于方式一在调用过程中需要应用自动化测试工具selenium,调用执行过程中增加了额外的时间开销。对比方式二与方式三,可以发现RESTful方式要比SOAP方式的服务调用时间短。
表3 三种测试执行方式在线性执行和优化执行下的时间结果统计
测试执行技术是实现测试完全自动化必不可少的重要组成部分。本文提出一个面向Web服务的测试执行引擎来解决服务调用接口不一致问题,并在分析单线测试序列的基础上优化执行操作,自动生成可并发测试脚本,提高测试执行效率。本文以Stack服务为实例,进行了实验研究,结果表明该测试执行框架可以有效地测试主流Web服务。在未来的研究中,将使用实际工业服务对测试执行框架进行验证,同时针对服务接口与规约操作不一致问题展开进一步的研究工作。