2、协作图与集成测试 2.1协作图的语法和语义 UML协作图[7]就是用来表示一组通过交互来实现某些行为的对象,可以用来可视化、详细描述、构造和文档化一个特定的对象群体的动态方面,也可以用来按交互中的角色及其关系对一个用例的特定的场景或控制流的实现进行建模。协作图描述了特定行为的参与对象的静态结构,以及参与对象之间的动态交互,可以用于不同的规约抽象级别,规约级协作图表示了类元角色、关联角色和消息,表示对象之间的可能的关系,而实例级协作图表示对象、链和激励,表示特定对象之间的关系。两者都描述了协作参与者之间的结构关系。如果要表示可能事件,一般用规约级协作图,其中没有条件和循环,实例没有取值范围或有许多可能的路径。实例级协作图用于描述属性或参量有具体的值的对象和链,可变大小多重性的对象和链的具体数目,或者是执行中的分支或循环的特定选择。协作图可以用来表示用例的实现或操作的实现,描述操作和用例的执行实现所处的语境、交互中行为序列。当协作图用于表示用例的实现时,只描述外部可见的动作及其顺序,用对象之间的消息交换来描述一个用例实现的场景。当协作图用来表示一个对象的操作的实现时,提供了更详细的信息,如操作的参数及其用途、参与变量的特征、关联上的约束、操作实现过程中对象的创建和破坏等。本文主要研究用于表示用例实现的实例级协作图,下面介绍实例级协作图符号及其语义。 协作描述了在一定的语境中一组对象以及用以实现某些行为的这些对象之间的相互作用。协作由静态部分和动态部分组成,静态部分描述在协作实例中对象和链可能担任的角色,是角色集合和其间关系,这些关系定义了结构方面的内容,动态部分是一个消息集合,包括一个和多个动态交互,表示在执行计算过程中不同时间里协作中的消息流, 这些消息定义了行为方面的内容。角色表示协作中对象和链的目的,类元角色定义了参与协作执行的对象在一个协作中扮演的角色,关联角色定义了一个类元角色和其他角色之间的关系,是参与协作执行的关联的描述,关联角色是链的子集,链是两个或多个对象之间的单向连接,是关联的实例。对象必须是位于关联相应位置的类的直接或间接实例。关联是两个或多个特定描述其实例间连接的类元之间的关系,参与关联的类元在关联内有有序的位置。在运行时,对象和链与协作中的角色绑定,在不同的协作中一个对象可以绑定到一个或多个角色。 交互是由在一个实现特定目标的协作内一组对象之间通信序列组成的行为规约。每个交互包含消息的偏序集,这些消息由类元角色通过关联角色交换。消息是激励的规约,是发送者和接受者之间的通信。消息指定了发送者对象和接受者对象扮演的角色,说明发送者对象对接受者对象应用何种操作。激励是传递消息期望发生的两个对象之间的通信,激励导致一个操作被激活、发出一个信号、导致一个对象被创建或终止。 本部分介绍一个客户通过ATM上验证用户卡和PIN有效性、建立会话的用例片断[9]的实例级协作图,如图1所示。协作图上存在条件消息说明考虑到许多不同的用例场景,涉及到多个对象之间的交互,其中每一个场景都对应协作图上的一条执行路径,这正是测试所要尝试的。图2表示了在这个协作中参与对象相应的类图。
一个描述用例实现的实例级协作图上用于建模的元模型包括:类元角色名、关联角色名、对象符号、链接符号、消息符号、链上的约束等。对象框能够表示参与协作中交互的对象,链表示这些对象之间的结构关系,消息的箭头形状表示协作对象间的通信模式和对象的执行特征, 消息标签表示对一个对象的操作调用的允许顺序的实例、操作的语义。在实际的建模活动中有些元素并不在模型上反映,例如图1的协作图只表示了协作中参与的对象和对象之间的消息,这是表示对象交互的最小元素集。本文关注协作图表示的动态行为的测试,前面提到协作图上表示交互的主要元素是消息,下面详细介绍消息。 协作图中的消息用带有消息标签的箭头表示,附在连接发送者和接受者的链上,链用于访问目标对象,箭头沿着链指向接受者,一个链上可以有多个消息,沿着相同或不同方向传递,消息的相关顺序由消息标签中的顺序号表示。按照UML1.5[5]中提出消息箭头有三种形状表示对象之间的通信方式和控制流类型:实线实心箭头表示同步消息,用于表示过程调用和嵌套控制流,每次调用增加一个嵌套层次;刺状箭头表示平面控制流,是异步通信,无嵌套,表示前驱-后继关系,指向下一步骤;虚线刺状箭头表示从过程调用返回。图1中的消 图1 ATM建立一次会话的实例的协作图 息都是带有刺状箭头的实线,表示前驱/后继关系。消息标签说明发送的消息、发送的条件、由消息激活的方法、相应的参量和返回值,以及交互中的消息顺序,包括调用嵌套、迭代、分支、并行和同步,完全格式消息标签的语法[5]中有详细的定义。其语法如下: [predecessor][guardcondition][sequence-expression][return-value-list]:=message-name(argument) [predecessor]表示前驱,在协作中前驱是用逗号隔开的顺序号列表,并后跟“/”;[guardcondition]表示线程同步的卫式条件;[sequence-expression]表示顺序项列表每一项表示交互中的一个嵌套层次,如果所有的控制并行则无嵌套;整数表示过程调用中的相邻高层中的消息的顺序,同一整数项中的不同消息在嵌套层是顺序相关的,同一前缀的消息顺序号构成序列。循环表示条件或迭代执行,表示根据条件执行零个或多个消息;[return-value-list]在有返回值的情况下表示消息返回值的名称,名称之间用逗号隔开,可以作为后续消息的参量;message-name 表示目标对象中引发的事件名称,可以用不同的方式实现,如操作调用;Argument 是消息引发的方法的参量列表。 图2 图1 ATM用户验证的类图 消息上的条件增加了消息的表达能力,也增加的消息复杂度,消息上的条件有几种形式:在一个新的调用层次开始,如图1中消息标签4.1[isMemberCard]feeAmount:=getfeeAmount()所示:4.1表示一个新的调用序列,条件成真时该层次的消息顺序发送,否则都不发送;在顺序发送的消息上,例如消息1.3中,[!isATMCard]条件表示二值条件,条件成真时其后的消息eject()执行一次,否则不执行,对其他消息没有影响;在一个表示迭代的嵌套调用消息开始,如消息3.1.1表示了一个迭代执行多个消息的情况,根据validPIN和n的取值可能发送1或2次消息。 这些协作图上的模型元素所表示的软件系统的不同方面的信息,都是系统的本质信息,需要在软件从设计到实现过程中必须保持的,因而软件实现中的行为的本质方面都会在设计中表示,所以可以从设计表示中获取行为方面的信息,用于确认软件实现中是否正确实现。 2.2场景路径 一个表示用例实现的协作图实现了用例的不同事件流,包括正常流和异常流,每个事件流称为一个场景,这些场景都被相应的协作图实现。一个协作图所表示的协作正是从一个外部事件触发开始,在参与协作的对象之间完成一系列的交互,最后正确的协作结束时都导致一个外部输出 ,对应于一个实际场景,也是一个线程执行的路径。要找到这些路径,一般的方法是将协作图转换为流图,然后在流图上使用图遍历的方法达到路径覆盖从而得到所有的路径。本文为了避免复杂流图的转换和不可行路径的遍历开销,试图直接从协作图上获取这些路径。我们从Paul C. Jorgenson[4]定义原子系统功能(ASF)和方法/消息路径(MM-path)得到启发,本文定义了一个场景路径(scenario-path): 定义 场景路径(scenario-path,s-path)是协作图上从一个没有前驱消息的消息开始,沿着消息的偏序执行序列,到达一个没有后继的消息结束的由消息相连的方法执行序列。 由于面向对象软件的事件驱动特性,软件的执行由事件开始,反映场景执行的场景路径由一个外部事件触发的消息(没有前驱消息)开始,表示路径入口,通过消息传递访问协作图中参与一个协作场景实现交互的对象的方法序列,达到一个引发端口输出事件的方法且该方法自己不再发出新的消息(没有后继)结束,到达路径出口,这时系统处于静止状态,等待另一个系统级端口输入事件开始进一步的处理。一个场景路径的线程的执行是一个原子执行,场景路径表示了协作图中的一个场景的线程执行的完整的踪迹,也是参与协作的对象之间的交互的踪迹,消息的传递激活对象方法的执行是对象之间控制流的转移,而路径中通过消息激活的方法调用的参数定义和使用、对象的创建、使用和撤销明显表示了一条在控制流上执行的数据流。 由于协作图在表示用例实现时,协作图上的场景路径是从第一条入口消息开始到所有能触发端口输出事件或没有后继消息的出口消息之间的所有可能的消息流。要获取场景路径上的消息流,可以通过消息之间的偏序关系以及决定消息流分支和循环的条件来实现。回顾协作图上消息及其顺序号的定义,整数表示过程调用中相邻高层中的消息顺序,同一整数项下的不同消息是表示一个前驱-后继式的平面控制流,嵌套的消息顺序号表示过程的调用控制流。消息顺序号隐含地表示了消息之间的偏序关系,因此可以在协作图上提根据消息的发送条件及其顺序号来跟踪场景路径。协作图中通过消息顺序号跟踪消息执行的顺序比顺序图中通过自上而下的生命线跟踪消息执行的顺序困难得多,但由于协作图更容易表示对象之间的动态连接关系,协作图不关注全局的消息执行顺序,只关注一个场景的执行路径上的消息顺序,这为我们识别场景路径提供了方便。协作图上的分支和循环导致了许多可能的路径,极端的情况下路径的数目可能到达一个庞大的数字,我们要达到协作图上的路径覆盖,采用简单的方法达到判定/循环覆盖。我们先将循环消息看作条件消息,在遍历场景路径的过程中,访问一条消息后,先找出该消息可能的直接后继,然后采用深度优先的方法选择一个直接后继继续遍历,当到达一个没有后继的消息时,就完成一个场景路径的遍历,回溯到下一个直接后继,继续遍历,只到所有的直接后继都已被访问,则完成了所有的场景路径的遍历。这样我们实现每条消息至少遍历一次,每个条件分支都至少遍历了一次,也避免了路径的穷尽覆盖。然后处理协作图上的循环,一般是给出绕过循环、循环一次、循环最多次以达到循环路径的覆盖。对于存在分支和循环的场景路径,例如图1所示的协作图上有7个条件判断和一个最大次数为2的循环,可能的路径有2*2*2*2*3*2*2*2=192条,但由于有5个条件判定各有一个分支直接导致场景路径结束,所以实际的场景路径应为1+1+1+1+1+3*2+2=13条,这13条场景路径对应相应软件功能的13个执行场景。 我们在协作图上遍历生成场景路径的同时,很容易基于路径覆盖准则获取相应场景执行过程中的控制流和数据流、覆盖路径的条件,然后可以确定每一路径所需要的输入和状态条件,当满足所有路径条件时线程就会沿着该路径执行,这正是我们定义集成测试用例所需要的信息,因此我们考虑将场景路径作为基于协作图的集成测试的基础。下面我们分析协作图表示的协作在实现中通过参与者之间的集成完成时可能发生的故障,这样便于我们研究针对检错为目的的测试。 2.3基于协作图的协作集成测试模式 协作图上描述的软件系统的消息的错误实现可能导致协作中表现的行为发生偏差,从而使最终实现与设计不一致,偏离软件规约规定的功能。例如由于消息名的编码错误、错误的参数、不正确的参数值、不正确的或缺少输出以及非预期的运行时绑定导致的错误方法调用;消息前驱约束实现错误导致发送者对象发送违反接受者对象前提条件的消息或者发送者对象发送违反接受者对象的顺序约束的消息;消息的发送者或接受者对象错误导致错误的对象和消息的绑定;参与者缺少功能或特征导致错误的对象引起正确的异常、正确的对象产生错误的异常。所以链上的消息箭头和约束、消息标签、对象等协作图上的元素未按设计正确实现会导致最终的软件与设计不一致,如果协作图的实现中存在错误,肯定在协作图至少一条路径上,要达到该错误,在未知该错误在那条路径上时,只能通过协作图上选择所有可能的执行路径,并导出能够跟踪这些路径的测试用例,并用它们来执行软件,来确定错误在哪条路径上,即哪条路径被错误实现了,从而可以调试软件,改正错误,以保证实现与设计一致。 这些可能发生的错误都是在参与协作的对象之间交互时发生,也就是系统在通过不同类的对象的方法之间集成实现系统的行为时发生,这种错误通常由集成测试来发现。软件集成测试有几种常用的集成测试模式(integration testing pattern):一次性组装、自底向上集成、自顶向下集成、协作集成、基干集成、层次集成等,但是考虑到面向对象软件时有的模式不一定有用,在面向对象的系统中,很难找到一个控制的层次结构,使得方便定义自底向上集成、自顶向下集成、层次集成等策略。在面向对象的环境中我们可以通过基于线程或基于使用的测试技术测试类的交互。这里交互的类可以是单个类及其实例、类簇、组件、子系统或系统。用协作图描述的系统协作在集成测试时,协作集成是最佳的测试模式[9],通过在协作中参与的组件来决定集成测试所有参与的对象。基于协作的集成测试需要检测协作的参与者之间的接口和交互,通过协作组织集成。每次处理一个协作,直到协作图上的所有协作都被测试。当所有构件的接口和构件之间的交互都测试完时集成测试完成。协作中包括的构件按照在一个处理线程、一个事件-响应路径来选择,要求系统中每个构件之间的消息已经被至少测试一次。我们前面定义场景路径正是一个线程完成协作执行的一个事件-响应路径,所以可能发生的错误会出现一条或多条场景路径上。 测试模式是一个测试策略,是建立在测试模型基础上的,而测试模型应该是被测软件系统的一个表示,能够从测试模型上获取测试需求,导出测试规约,从而生成测试用例。协作图是对象、组件、子系统、系统范围需求的良好来源,对系统的一个有限片断来说,提供了实现的抽象视图,它通常是过多或过少细节之间的一个良好的折中,可以用于在不同抽象级别和粒度级别建立软件系统的模型。由于协作图是描述的对象之间的交互,而系统的功能正是通过这些交互实现的,所以要考虑将设计描述的协作图作为生成集成测试的测试用例的测试模型。协作图上包括了对象间传递的消息及其顺序,这正是设计级的控制流和数据流信息,以往数据流和控制流只能从程序源码中分析得到。数据流和控制流对生成测试有很大的作用,所以我们利用协作图生成测试用例,就是要从协作图上提取出相应的数据流和控制流信息,利用传统的数据流、控制流生成测试用例的方法,生成可用于集成测试的测试用例。所以本文使用作为系统设计描述的协作图作为测试行为的测试模型,避免重新构造测试模型或者进行模型转换。 2.1中分析了协作图中描述的信息,这也是作为测试模型的协作图中包含的对生成测试用例有用的信息,这些信息是规约在转变成实现时必须保持的,这些信息也是测试时要确认在软件实现中是否正确保持的设计信息,因此这就是测试工作第一步要获取的测试需求。测试需求的正确实现要求也就是测试规约,它规定了能让软件执行并正确反应测试需求的测试用例。如果我们能够用足够的测试用例执行了程序,并证明协作图上所有测试需求都在软件中正确保持了,就可以说测试充分了,本文主要关注协作图表示的系统的动态交互行为,而协作图上用于表示交互行为的主要是消息及其偏序序列,所以本文研究的测试方法的充分性准则是协作图上所有的消息及其偏序关系都被测试用例覆盖。表1中的协作图的集成测试需求说明至少有一个测试用例测试对应的关系来检查测试需求是否满足。 符号 关系 集成测试需求 实线实心箭头 扁平控制流 发送者与接受者之间的偏序 实线刺状箭头 过程调用 条件过程调用
迭代过程调用 递归过程调用 发送者发送消息到接受者并返回 发送者发送消息到接受者并返回, 发送者不发送消息到接受者 发送者重复发送消息到接受者并返回 发送者发送消息到自己 虚线刺状箭头 从过程调用返回 可不考虑 表1 消息对应的集成测试需求 一个场景路径上由消息激活的方法序列表示了我们要测试的行为,路径上对象间的消息传递描述了要实现相应的功能对象间必要的交互,协作图上场景路径的构造能够满足表1描述的协作图对应的集成测试需求,这样我们就可以把问题转换到满足协作集成测试需求的场景路径的分析和处理上。我们在协作图上遍历生成场景路径的同时,很容易基于路径覆盖准则获取相应场景执行过程中的控制流和数据流、覆盖路径的条件,然后可以确定每一路径所需要的输入和状态条件,当满足所有路径条件时线程就会沿着该路径执行,测试用例的定义需要满足路径条件的特定输入和状态。为了确定测试用例的值,需要分析每条路径上的谓词,从第一个谓词开始,选择满足相应路径条件输入和状态值。直到完成所有的路径条件,最后确定每个测试用例的预期结果,包括方法调用序列。第3部分详细描述从协作图构造场景路径,然后基于场景路径生成测试用例的方法。
|