UML软件工程组织
|
一个基于UML协作图的集成测试用例生成方法(三) |
王林章 来源:中国软件行业协会 |
3、基于协作图生成集成测试用例的方法 3.1研究假定 为了有针对性地解决从协作图生成测试用例的问题,本文作出如下假定和要求: (1)假定协作图描述的协作与用例图描述的规约是一致的。模型本身的验证是通过非形式化的复审和形式化的模型检验方法进行的,已超出本文的研究范围;如果协作图上的场景路径集不能覆盖所有消息,则说明协作图本身有错误; (2)假定系统中的对象都是自行开发的,不包括第三方组件对象,文中的对象可以是不同粒度的对象(类的实例、类簇、组件、子系统、系统),也就是说我们在分析任意对象或类时,在必要的情况下都可以获取其规约和内部详细设计信息,包括功能和结构信息;对于第三方组件的集成测试,[13][17]介绍了详细的测试方法; (3)我们研究的测试是针对检错进行的,确认软件是否正确实现了设计,协作图上的结构关系是否被正确实现,[15]中已有相应的静态检验方法,所以本文只研究动态交互行为的测试; (4)本文为了明确解决问题,假定消息类型只有普通消息、条件消息、循环消息,而且只存在顺序循环,不存在嵌套循环。 3.2方法概述 基于协作图的集成测试(Collaboration diagram-based Integration Testing, CIT )方法集合了传统的白盒测试方法和黑盒测试方法,用于测试协作图中参与协作的对象之间通过消息的交互,对每个协作图处理一次,得到相应的测试用例集。首先分析协作图,提取协作图中表示的所有元素,根据消息的顺序号和消息的条件,找到每一条消息的直接后继消息,然后根据场景路径的定义,使用深度优先方法遍历消息及其直接后继直至到达无直接后继的消息从而生成场景路径,然后回溯到没有被访问的直接后继,重复上述方法找到所有的场景路径;在访问消息获取场景路径的同时,获取该路径的方法调用序列、参数和路径条件,将这些集成测试的关键因素用范畴-划分方法定义为方法序列、环境条件、系统输入、系统输出等范畴,结合该协作片断的用例规约和类图中的定义生成这些范畴的可能选择,然后结合路径约束条件在这些范畴的划分中确定选择项的合理组合,这样我们就等到了该场景路径完整的测试用例,包括外界输入、交互输入、预期方法调用序列、后条件、预期输出。对协作图中的所有场景路径都构造了测试用例,就形成了协作集成测试用例集。这样在实际执行集成测试时不但可以直接观察到系统级的输入作用下协作实现过程中的实际输出,还能够通过动态插装方法在代码中加入不影响软件功能的观察代码[15],使测试人员能够观察到实际协作执行时的方法调用序列和数据流的定义和使用,然后通过比较最终系统实际执行时输出与预期输出的一致性决定该协作实现的功能是否正确,通过比较应该发生的方法调用序列和实际执行时观察到的方法调用序列是否一致,确定协作表示的交互行为是否正确,从而从整体上对协作图描述的系统行为的实现是否与设计一致,从而完成协作的集成测试。 本方法可以以增量的方法进行,最终生成的测试用例的具体程度,与相应UML模型图的设计精化程度相关,因为协作图描述的场景的详细程度与测试用例的表达能力直接相关。 3.3 UML协作图生成测试用例的算法描述 本文的方法能够从协作图规约文档直接生成测试用例,主要描述分析协作图规约文档提取集成测试需求信息并生成消息后继表的算法UMLspecificationparser(),然后为每个消息确定其直接后继消息的findsuccessor()算法,根据消息后继表遍历所有场景路径获取测试用例规约信息的spathgenerator()算法,以及从测试用例规约使用范畴划分方法确定测试用例输入值、预期输出值、预期输出行为的算法testcasegenerator()。UMLspecificationparser()算法从协作图从获取集成测试需求信息是通过对UML协作图规约的文本文件(如Rational Rose 的MDL文件)进行分析完成的[11],在我们的工具中实现相应的功能,本文对分析算法不作详细描述。我们用邻接表定义消息后继列表,将分析结果信息按消息顺序号升序记录到messagelist的表头中,以便下一步处理。我们定义消息后继列表如下: messageitem{ mid:string;//消息顺序号 mguardcondition:string;//消息卫式条件表达式 mlabel:string;//消息标签 msender,mreciever:object;// 消息发送者对象,消息接收者对象 mmethod:method;//消息激活的方法 mtype:[flat flow, procdure call, call return];// 根据消息的箭头及线型划分的消息类型 link:pointer of msuccessor; } msuccessor{ mid:string;//后继消息顺序号 mscondition:string;//选择该后继的条件表达式 next:pointer of msuccesor } messagelist:messageitem[n]; findsuccessor()算法为消息生成直接后继列表的并记录到消息的后继链表中,描述如下: findsuccessor(messagelist){ //找到所有消息的所有可能后继和每个后继条件 //输入:消息后继表messagelist //输出:消息后继表messagelist //出口准则:所有消息都处理完 for i:=1 to n do { if i= =n 或者messagelist[i]为端口输出事件触发消息 messagelist[i].link=nil;i:=i+1; //最后消息或触发端口输出事件的消息无后继消息 else{ if messagelist[i+1]是普通消息 则messagelist[i]的直接后继为messagelist[i+1]; else{ k:=1; do while messagelist[i+k]是条件消息{ messagelist [i+k]是messagelist [i]的直接后继,且条件为messagelist [i+k]的条件取真 if messagelist [i+k]是循环或嵌套层次的第一个消息{ t:=该层次的消息数;messagelist [i+k+t]是messagelist [i]的直接后继; 条件为messagelist [i+k]的条件取假; k:=k+t;}//条件取假时循//环或嵌套内的消息都不发送 else{ messagelist [i+k+1]是messagelist [i]的直接后继; 条件为messagelist [i+1]的条件取假; k:=k+1;//不发送 }endif }enddo }endif i:=i+1; }endif }endfor; }endfindsuccessor; spathgenerator()通过访问消息后继表中消息及其直接后继生成场景路径,在遍历场景路径时同时记录路径上相应的控制流和数据流信息,执行完毕得到一个场景路径集spath相应的控制流数据流测试规约。其算法描述如下: s-pathgenerator(m:msuccessorlist){ //输入:messagelist; (消息后继表) //输出:spath;(场景路径集) spath{ sid:integer;//场景路径顺序号 pathcondition: list of gardcondition; //场景路径的实现条件 mlist:list of message;//场景路径上的消息流列表 parameters:list of variable;//场景路径上定义和使用的变量列表 userinputparameters: list of variable; //场景路径上外界输入参数列表 methodsequence:{objectname,methodname,next};场景路径上由消息激活的方法序列 }[n]; i,j:integer; i:=1; j:=1; Sid:=j;//场景路径序号 //递归访问消息及其直接后继 访问消息m,在消息标签中取出并记录返回值、参数、调用的对象方法; 记录消息条件到路径条件中; if m的后继消息为空 { 一条场景路径结束;j:=j+1;return;}//回溯到m[i]的直接前驱继续 else{ k:=1; while m.link<>nil do { //m的后继消息列表不空 successor:=m.link; spathgenerator(successor) //深度优先遍历该消息及其后继消息 m.link:=successor->next;//下一后继消息; }endwhile }endif; }endfind; testcasegenerator()选定遍历一个场景路径过程中生成的控制流和数据流,定义相关范畴,生成测试用例的规约。消息激活的方法序列正是我们要测试的对象间的交互,是软件执行时表现出来的对象之间的控制流传递行为,是对象之间通过消息交互的结果,定义为交互范畴Interaction Category;输入参数列表为测试该场景时外界的输入,定义为输入参数范畴input parameter Category;我们通过对参与协作对象的类图的分析,以及用例描述,可以获取我们所研究的交互行为定义、输入参数的定义、其他影响消息执行的变量的定义、系统环境条件的定义等,从而可以获取这些系统环境条件、输入参数、方法行为的可能选择,然后结合。 利用范畴-划分思想,用相应场景路径的路径条件和加在消息上的约束、以及系统本身的属性约束来限定这些选择,排除无意义和有冲突的值,然后通过这些不同的范畴的不同选择的可能组合作为一个场景路径的测试用例,这样每个场景路径生成一个集成测试用例。具体算法描述如下: Testcasegenerator(){ //输入:spath[n] //输出:tcsuite[n]{ envirnmentpara,input(inputpara,paravaliue),expectoutput,postcondition} i:integer; for i:=1 to n do { tcsuite[i].postcondition:=spath[i].pathcondition; tcsuite[i].environmentpara:={满足路径条件的环境条件} tcsuite[i].input.inputpara=spath[i].userinputsparamenters; 选择对本场景路径条件有影响的参数确定其可能的取值; tcsuite[i].input.paravalue={符合路径条件的输入参数可能的取值}; 取出spath[i]的方法激活序列,分析每一个方法的定义,确定其可能的行为,选择符合场景路径条件的行为,并将方法序列的执行过程的输出序列存到tcsuite[i].expectoutput中; i:=i+1; }endfor; }endtestcasegenerator; 为说明问题,我们选一条最长路径spath[5](消息序列为:1.1,1.2,2.1,2.2,2.3,4.1,4.2,4.3,5)来演示使用上述方法生成覆盖该路径的测试用例的过程。,从该路径获取相关范畴如下: Tcsuite[5] Pathcongdition:!notATMCard, !isStolen, !accountClosed, validPIN&&n=1,!declineFee Environmentcondition: ATM卡,非成员卡,卡没有被盗,帐户没有关闭 parameter category: inputPIN //影响validPIN 123456 [property validPIN][if !notAtmCard] 564821 123258 …….. deClineFee //影响deClineFee T [property declineFee][if !isMemberCard]] F interaction behavior category: session.beginSession(), 插卡事件创建session对象 cardSlot.isATMCard(), 判断是ATM卡,读取卡号,定义cardNum,notATMCard=F[if !notATMCard] 判断不是ATM卡,notATMCard=T screen.prompt(), 屏幕提示“please input PIN”,n=1[if !notATMCard][property n] 屏幕提示“please input PIN”,n=2[if !notATMCard] [property n] 屏幕提示“please input PIN”,n=3[if !notATMCard] [property n] numkeyPad.getEntry(), 输入正确的PIN inputPIN=’123456’[if n=1] 输入错误的PIN inputPIN<>’123456’[if n=1] 输入正确的PIN inputPIN=’123456’[if n=2] and [if !validPIN&&n=1] 输入错误的PIN inputPIN<>’123456’[if n=2] and [if !validPIN&&n=1] 输入正确的PIN inputPIN=’123456’[if n=3] and [if !validPIN&&n=2] 输入错误的PIN inputPIN<>’123456’[if n=3] and [if !validPIN&&n=2] bank.checkCard(), 为被盗ATM卡验证PIN ,isStolen=T[if !notATMCard] 为帐户关闭的ATM卡验证PIN,accountClosed=T [if !notATMCard] 为合法ATM卡错误输入PIN验证身份,accountClosed=F, isStolen=F ,validPIN=F[if !notATMCard&n<4] 为合法ATM卡正确输入PIN验证身份,accountClosed=F, isStolen=F ,validPIN=T[if !notATMCard&n<4] bank.getFeeAmount(), 获取非成员卡用户交易服务费金额,定义feeAmount [if !notATMCard&&ValidPIN&&n<4 and !isMemberCard] screen.prompt() 屏幕提示“Fee Payed:XXX” numkeyPad.getEntry(), 输入同意付费,declineFee=T 输入拒绝付费,declineFee=F screen.prompt() 屏幕提示“please select transaction”[if !notATMCard&ValidPIN&&n<4] and [if isMemberCard or !isMembercard&!declineFee] 这样我们符合spath[5]路径条件的相应的一个测试用例tcsuite[5]的最后结果为: Test case 5: 外部事件:插卡 environmentcondtion: ATM卡,非成员卡,卡没有被盗,帐户没有关闭 input category: inputPIN=’123456’ //输入有效PIN declineFee=F //输入同意付费 interaction behavior category: session.beginSession(), 为ATM卡合法持卡人建立会话 cardSlot.isATMCard(), 判断是ATM卡,读取卡号cardNum, notATMCard=F screen.prompt(), 屏幕提示“please input PIN”,定义n=1 numkeyPad.getEntry(), 输入正确的PIN,inputPIN=’123456’ bank.checkCard(), 为合法ATM卡正确输入PIN验证身份,定义accountClosed=F, isStolen=F ,validPIN=T,ismemberCard=F; bank.getFeeAmount(), 获取非成员卡用户交易服务费金额,定义feeAmount=xxx screen.prompt() 屏幕提示“fee Payed XXX” numkeyPad.getEntry(), 输入同意付费 定义declineFee=F screen.prompt() 屏幕提示“Please select transaction” 预期的方法调用序列:session.beginSession(),cardSlot.isATMCard() ,screen.prompt() ,numKeypad.getEntry() ,bank.checkCard() ,bank.getFeeAmount(),screen. prompt (),numKeypad.getEntry(),screen. prompt () 后条件: inputPIN=’123456’,!notATMCard, !isStolen, !accountClosed, validPIN&&n=1, !declineFee 预期输出: 屏幕提示“please input PIN” 屏幕提示“fee Payed XXX” 屏幕提示“Please select transaction” 我们可以用同样方法为属于同一个用例的其他协作图构造了测试用例,则该用例的集成测试集构造完成。然后,可以用增量方式为组件、子系统、系统构造完整的集成测试用例集,而且可以用自动的方式进行。可以用[15]中的插装方法按照每一场景路径上的方法调用顺序,对待测试实现进行插装,这样在执行时可以观察到软件用相应测试用例运行的结果,并将观察到的实际执行结果与测试用例中的预期结果进行比较,如果一致,说明相应场景的实现与设计是一致的,否则,存在错误,并且错误在协作图的相应场景路径上,可以通过实际结果与预期结果发生不一致的地方定位错误,然后调试修改。 3.3算法实现工具框架 为了支撑上述测试用例生成方法的自动化,我们设计一个基于UML设计模型图的集成测试用例的自动生成工具UMLTGF,我们仍然使用UML为UMLTGF建模,本文给出UMLTGF的类图,如图3。相应的功能描述如下: 1、UML模型分析器。能够直接读取UML协作图规约的文本文件(如MDL文件)[11],进行解析,并获取对象角色、对象的属性和方法,链、约束,消息流和消息标签,存入相应的中间表,以便测试用例生成器使用; 2、协作集成测试用例生成器。根据消息的顺序号和条件,跟踪消息流,生成每条消息的直接后继,然后通过遍历消息及其直接后继的方法生成协作图上每个用例场景对应的协作的场景路径,同时可以获取场景执行的控制流和数据流,并用范畴-划分方法生成相应场景路径对应的集成测试用例。输入为从协作图中提取的信息中间表,输出为生成的测试用例集合; 3、测试用例管理。为使得生成的测试用例集可复用、可维护、无冗余,对每个测试用例设计了增加、修改、删除操作,用来在测试用例生成过程中管理这些测试用例集。 图3 UMLTGF类图 |
版权所有:UML软件工程组织 |