西安交通大学 刘海岩 1
第 10章 面向对象测试
面向对象测试的特点
面向对象的测试策略
面向对象软件的测试用例设计
RUP的测试活动西安交通大学 刘海岩 2
10.1 面向对象测试的特点面向对象测试的整体目标(以最小的工作量发现最大数量的错误)与传统软件测试的目标是一致的。但是
OO程序的性质改变了测试策略与战术。
1,传统测试主要是基于程序运行过程的,即选择一组输入数据运行被测程序,通过比较实际结果与预期结果从而判断程序是否有错。而 OO程序中的对象通过发送消息启动相应的操作,并且通过修改对象的状态达到转化系统运行状态的目的,同时,在系统中还可能存在并发活动的对象。应此传统的测试方法不再适应。
2,传统程序的复用以调用公共模块为主,运行环境是连续的。而面向对象复用很多是用继承实现的,子类继承过来的同名操作有新的语境,必须要重新测试。随着继承层次的加深,测试的工作量和难度也随之增加。由继承支持的多态的特性同样给测试带来了难度。
西安交通大学 刘海岩 3
3、面向对象软件的开发是渐进、演化的开发,从分析、
设计到实现使用相同的语义结构(如类、属性、操作、
消息)。因此要扩大测试的视角,对分析模型、设计模型进行测试。例如,在分析模型中定义了一个 无用的属性,围绕着这个属性可能会带来以下错误:
在 分析模型中:
定义了一个与该属性有关的操作:
导致了不正确的类关系:
为共享属性和操作创建了不必要的子类:
为适应该属性和操作刻画了其类和系统的行为。
如果问题在分析阶段未被发现,再将错误继续传播,
使得设计模型可能存在:
与该类有关的不合适的子系统或任务的划分:
与该无用属性有关操作的算法设计:
与该无用属性有关操作的接口及消息模式。
西安交通大学 刘海岩 4
如果问题在设计阶段仍未被检测到,并传送到编码活动中,则大量的工作将被花在生成那些实现一个不必要的属性、不必要的操作、不必要的消息通信以及很多其它相关问题的代码。
由于分析设计模型不能被执行,所以不能进行传统意义上的测试。只能通过正式技术复审来检查分析模型和设计模型的一致性。
4,面向对象开发工作的演化性使面向对象测试活动也具有演化性。每个构件产生过程中,单元测试随时进行,
迭代的每一个构造都要进行集成测试,后期迭代还包括大量的回归测试,迭代结束时进行系统测试。
是否设计模式的使用将减轻 OO系统的繁重测试?
Binder认为每次复用是一个新的使用语境,需要重新谨慎的测试。为了获得 OO系统的高可靠性,可能需要更多的而不是更少的测试。
西安交通大学 刘海岩 5
10.2 面向对象的测试策略传统的测试策略是从小型测试开始,逐步走向大型测试,
即从单元测试开始,逐步进入集成测试,最后进行系统测试。
在传统测试中,单元测试集中在最小的可编译程序单位(子程序、过程、函数),一旦这些单元都被独立测试后,被集成到程序结构中进行一系列的回归测试,以发现由于模块的接口和新单元加入所导致的副作用而带来的错误。最后,对系统整体进行测试以发现需求中的错误。
1、单元测试(类或对象或组成的小簇)
OO语境中,单元的概念发生了变化。封装驱动了类或对象的定义,即每个类或对象封装了属性和操作这些属性的服务,最小的可测试单位不是个体模块,而是封装的类或对象。
类包含一组不同的操作,并且某个特殊操作可能作为类的一部分存在(如子类中继承的操作),因此,单元实际上是类或若干相关的类组成的小簇。
西安交通大学 刘海岩 6
单元测试不再孤立的测试单个操作(这是传统的单元测试的视角),而是将操作作为类的一部分。例如:
命令
execute()
粘贴命令
execute()
拷贝命令
execute()
execute由基类定义并被一组子类继承,每个子类的
execute被应用于每个子类定义的私有属性和操作的语境内,
因此,仅在基类内测试 execute
是无效的,应该在每个子类的语境内测试 execute。
单元测试若用于测试不发生请求的类(如“栈”类,其中操作有,pop(),push(),empty())时,同样要设计驱动程序,封装在一个测试类(包)中,测试类负责运行测试用例并给出结果,每个测试用例用一个操作名表示;单元测试如果测试发生请求的类,则需要设计桩程序,封装在桩类中。
西安交通大学 刘海岩 7
单元测试主要使用的图模型是:类图、类的状态图、活动图。
2、集成测试(大簇、构件、子系统)
这里的构件或子系统应该与系统的体系结构相对应。集成测试主要以检查这些构件、子系统的接口为目的。 对于类之间的集成,Roger S,Pressman认为传统的自顶向下和自底向上集成的测试策略没有意义。他提出了两种集成测试策略:
( 1)基于线程的测试( thread-based testing)
集成一组相互协作的对某个输入或事件作出响应的类,
每个线程被分别测试,并使用回归测试以保证没有副作用产生。
( 2)基于使用的测试( use-based testing)
按层次测试系统。先测试不依赖服务器的独立类,如管理和显示数据的类,然后测试依赖独立类的其他类。逐步增加依赖类,直到测试完整个系统。
西安交通大学 刘海岩 8
对于子系统之间的集成,如果系统划分为层次结构,
则可以按自顶向下或自底向上集成,同时也需设计驱动类和桩类。如:一个 OO系统的结构为:
用户界面 (A)
应用逻辑 (B)
访问数据库 (C)
网络通信
(D)
应用系统的一个结构该系统可以采用自顶向下、自底向上或三明治式进行集成测试。见下图。
西安交通大学 刘海岩 9
UI层桩 桩
UI层应用层桩 桩
UI层应用层数据库 网络数据库层 网络层驱动 驱动数据库 网络应用层驱动 驱动
UI层桩 桩数据库层 网络层驱动 驱动自顶向下 自底向上 三明治式西安交通大学 刘海岩 10
Test A
Test B
Test C
Test D
Test A,B
Test B,C
Test B,D
Test A,B,C,D
单元测试 集成测试 集成测试测试过程( UML活动图)
集成测试使用的图模型是:顺序图、协作图、活动图(概念层)
西安交通大学 刘海岩 11
3、确认测试在确认和系统测试层次,和传统的一样。测试的内容主要集中于用户可见的动作和用户可识别的系统输出
(用户可见的功能),以及系统性能等其他需求。测试人员应该根据需求说明和用例模型设计测试用例。
确认测试使用的图模型主要是用例图。
西安交通大学 刘海岩 12
10.3 面向对象软件的测试用例设计传统测试用例设计是由软件的输入、加工、输出视图或个体模块的算法细节驱动的,面向对象测试关注于设计合适的操作序列以测试类的状态和用例的实现。
1、传统方法的可用性白盒测试:用于类级别的测试。测试类中封装的操作,
检查类的状态以确定是否存在错误。
黑盒测试:集成测试、确认测试。构件、子系统是黑盒。测试序列跟踪跨越类协作的操作流。
2、类级别测试用例设计(单元测试)
着重于单个类及封装的操作。可按照以下方法设计用例:
西安交通大学 刘海岩 13
( 1)随机测试考虑一个银行应用程序,其中 account类有下列操作:
open,setup,deposit,withdraw,balance,summarize,
creditLimit和 close,但问题的性质隐含了一些限制(例如,
账号必须在其它操作可应用前被打开,在所有操作完成后被关闭)。一个 account实例的最小行为生命历史包括下面操作:
open,setup,deposit,withdraw,close,表示了
account的最小测试序列。然而大量的其它行为可能在下面序列中发生:
open,setup,deposit,[deposit | withdraw | balance |
summarize | creditLimit]n,withdraw,close
一系列操作序列可以随机产生,例如:
测试用例 1,open,setup,deposit,deposit,balance,
summarize,withdraw,close
西安交通大学 刘海岩 14
测试用例 2,open,setup,deposit,withdraw,deposit,
balance,creditLimit,withdraw,close
可随机选取其它的测试序列以测试该类对象不同的生命历史。
( 2)划分测试( partition testing)
可以减少测试类所需的测试用例的数量,采用与传统测试的等价划分相同的方式,即输入、输出被分类,为处理每个类别设计测试用例。划分类别的具体方法是:
基于状态的划分基于类操作改变类状态的能力来对类操作分类。类中有的操作改变类的状态(如 account类中的 deposit和
withdraw ),有的操作不改变类的状态(如 balance,
summarize和 creditLimit )。因此分别独立测试改变状态的操作和不改变状态的操作。
西安交通大学 刘海岩 15
基于属性的划分根据操作使用的属性来划分类操作,即使用相同属性的操作划分在一个等价类中。如 account类中,以
creditLimit来定义划分,操作被定义成 3个类别:
① 使用 creditLimit 的操作,
② 修改 creditLimit 的操作,
③ 不使用或不修改 creditLimit的操作。
然后对每个划分设计测试序列。
基于操作类别的划分如在 account类中的操作可被分类为:
初始化操作( open,setup)、计算操作( deposit,
withdraw)、查询操作( balance,summarize,
creditLimit)和终止操作( close)。
西安交通大学 刘海岩 16
3、类协作测试用例的设计(集成测试)
测试类或构件被组装后相互之间能否正常交互完成指定的功能。使用 use-case作为测试的主要驱动,顺序图、协作图为测试提供帮助。
和单个类一样,可通过应用随机和划分方法以及基于
use-case场景和行为模型导出测试用例。
( 1)随机测试
Kirani等人建议用下面的步骤生成多个随机测试序列:
对每个客户类,用类操作列表生成随机测试序列,这些操作将发送消息给其他服务器。
对生成的每个消息,确定在服务器对象中的协作者类及对应的操作。
对服务器对象中的已经被来自客户对象的消息调用的每个操作,确定该操作向协作者发送的消息。
西安交通大学 刘海岩 17
对每个消息,确定下一层被调用的操作并结合这些操作到测试序列中。
如,某一个应用问题的类协作图如下:
A B C
DE
x1,x2,… x3
x4
对 B的随机测试序列可能是 x1,x2,…,为了考虑涉及到该测试的协作者,要考虑上述序列中每个操作相关联的消息。设 B必须与 C协作(需执行 x3)以执行 x1,B与 D协作
(需执行 x4)以执行 x2。因此,对 B的测试序列应该是:
x1,[ x3],x2,[x4],…
测试序列跟踪跨越类协作的操作流。 基于用例的实现是产生随机测试序列的基础。
西安交通大学 刘海岩 18
( 2)划分测试类似于单个类划分测试方法,但需扩展测试序列以包括那些通过发送给协作类的消息而激活的操作。另一种方法是基于特殊类的接口来划分测试。如上图,B接收来自类 A和类 E的消息,可以通过将 B中的方法划分为服务于 A和服务于 E的操作来测试。
( 3)从行为模型导出的测试类的 STD可用于帮助导出测试类(和那些与其协作的类)的动态行为的测试序列。下图是银行应用系统
account类的 STD。
所涉及的测试应覆盖所有的状态,即操作序列应该导致 account类的转换穿越所有允许的状态。
西安交通大学 刘海岩 19
测试用例 1,open,deposit(initial),withdrawal(final),
close (最小测试序列)
测试用例 2,open,deposit(initial),deposit,balance,
credit,withdrawal(final),close
测试用例 3,open,deposit(initial),deposit,withdraw,
accntInfo,withdrawal(final),close
setup acct working acct
nonworking
acct
open
deposit
(initial) deposit
balance,
credit,
accntInfo withdrawal(final)
close
withdraw
西安交通大学 刘海岩 20
对其中的每个方法至少发送一个消息,使对象从 α状态到 ω状态,表明经过的所有方法均是可操作的。
可以使用,宽度优先的方式” 遍历 STD:
一个测试用例测试单个状态转换,当测试新的转换时,仅使用以前被测试过的转换。
4、其它需要考虑的问题以上测试用例的设计主要考虑选取合适的操作序列,
还要考虑操作的参数,在选择参数时可对参数划分等价类,每个输入参数属于一个等价类,同时还需考虑参数的边界情况。
西安交通大学 刘海岩 21
10.4 RUP的测试活动
RUP建立的测试活动主要是执行并评估测试模型所描述的测试。其中,测试模型是包括以下内容的集合:
测试用例:可设计一张表:每一行是一个测试用例,
每一列有用例的输入数据、预期结果、实际结果和测试条件。
测试规程:详细描述了怎样使用测试用例。一个测试规程可能用于不同的测试用例,但有时一个测试用例可能需要多个测试规程。(多对多关系)
测试构件:为了实现系统测试自动化而设计的程序构件,有时也称“测试驱动程序”。
RUP的测试活动具体有以下几方面:
西安交通大学 刘海岩 22
1、制定测试计划规划一次迭代中的测试工作,包括:
描述测试策略
估算测试工作所需的人力以及系统资源
制定测试工作的进度制定测试计划的输入和结果见下图。
系统是不可能完全被测试的。每个测试用例、规程和构件的开发、执行及评估都需要花费时间和金钱。测试设计的准则是以最少的重复来测试最重要的用例并对风险性最大的需求进行测试。
西安交通大学 刘海岩 23
制定测试计划的输入和结果需求补充构架描述测试计划用例模型设计模型制定测试计划测试工程师分析模型实现模型
(测试策略与进度)
西安交通大学 刘海岩 24
2、设计测试用例设计测试用例的输入和结果需求补充构架描述测试规程用例模型设计模型设计测试用例测试工程师分析模型实现模型测试计划测试用例西安交通大学 刘海岩 25
( 1)设计集成测试用例用于验证构件被组装成包或子系统后相互之间能否正常交互。大多数集成测试用例可由用例实现 -设计导出,
因此,设计测试用例时,首先考虑用例实现的交互图,
从中选择若干组感兴趣的场景 — 参与者、输入信息、输出结果和系统初始状态的组合。当执行相应的集成测试时,可以捕获到系统内对象之间的实际交互(比如,通过跟踪打印输出或者通过单步执行),将中间结果与交互图进行比较。
( 2)设计系统测试用例用于测试系统功能整体上是否正确,在不同条件下的用例组合的运行是否有效。这些条件包括不同的硬件配置(处理器、基本内存、硬盘等)、不同程度的系统负载、不同数量的参与者以及不同规模的数据库。
西安交通大学 刘海岩 26
测试人员在设计测试用例时,应对以下用例组合的优先级进行排序:
执行并行功能时需要的用例组合。
可能被并行执行的用例组合。
如果并行运行,有可能相互影响的用例组合。
包含多进程的用例组合。
经常性的、并有可能以复杂的和不可预知的方式消耗系统资源(如进程、处理器、数据库以及通信软件)的用例组合。
在设计测试用例时,还要考虑事件流和特殊需求。
( 3)建立测试规程测试人员应根据测试规程对每个子系统使用一个或多个用例进行测试(也可一个用例有多个规程)。测试规程随着测试活动的进展可能需要修改,用来说明如何执行一个新的或发生了变化的测试用例。
西安交通大学 刘海岩 27
3、实现测试尽可能的建立测试构件以使测试规程自动化。
测试规程实现模型 实现测试构件工程师测试用例测试构件测试实现的输入和结果西安交通大学 刘海岩 28
实现测试构件有两种方法:
( 1)依赖于测试自动化工具。测试人员根据测试规程,
在自动化工具环境中执行测试规程所描述的动作,测试工具会自动记录这些动作。构件工程师整理这些记录,
并作适当调整,生成测试构件。这些测试构件通常是以脚本语言实现的,如 Visual Basic的测试版本。
( 2)把测试规程作为编程工作的主要规格说明,使用编程语言开发测试构件。需要有高超的编程技巧和责任心。
西安交通大学 刘海岩 29
4、执行集成测试对一个迭代内创建的每个构造执行集成测试。
测试规程实现模型 执行集成测试集成测试人员测试用例集成测试的输入和结果测试构件缺陷西安交通大学 刘海岩 30
集成测试按以下步骤进行:
( 1)对每一个测试用例执行测试规程(手工或自动),
实现与构造相关的集成测试。
( 2)将测试结果和预期结果相比较,研究两者的偏离原因。
( 3)将缺陷报告给相关的构件工程师,,由他们对缺陷进行修改。
( 4)将缺陷报告给测试设计人员,由他们对测试结果和缺陷类型进行统计分析,评估整个测试工作的结果。
西安交通大学 刘海岩 31
5、执行系统测试当集成测试已表明系统满足了当前迭代中所确定的集成质量目标时,就可以开始进行系统测试。系统测试的的输入和结果同集成测试。系统测试中发现的问题反馈给测试设计人员和相关工作流的负责人员。
6、评估测试测试人员将一次迭代内测试工作的结果和测试计划建立的目标进行对比,做出评估。
测试计划测试模型 评估测试测试工程师测试评估的输入和结果缺陷测试评估西安交通大学 刘海岩 32
测试人员确定一些度量标准,评估软件的质量水平,
并确定还需要进一步做多少测试工作。
测试设计人员尤其看重两条度量标准:
测试完全性。由测试用例的覆盖率和测试构件的覆盖率导出,即用测试用例使用数量和测试构件代码执行数量来衡量测试的完全性。当然,还要检查非功能方面的测试用例。
可靠性。根据已发现的缺陷进行缺陷趋势分析,测试人员创建缺陷趋势图,以阐明特定类别的缺陷在时间跨度上的分布。还要创建能够描述在时间跨度上测试成功率(即已达到预期结果的测试的比率)的趋势图。
基于缺陷趋势分析,测试设计人员可以提出进一步的建议,例如:
西安交通大学 刘海岩 33
如果可靠性测量指出系统还不够成熟,就要执行额外的测试来查找更多的缺陷。
如果对当前迭代所设置的质量目标过高,就要降低测试准则。
把系统中质量已过关的部分分离出,并把它们作为当前迭代的结果交付出去,没有达到质量标准的部分必须加以修改,然后再进行测试。
测试设计人员在测试评估描述中阐述测试的完整性、
可靠性并提出行动建议。
西安交通大学 刘海岩 34
7、测试小结测试的主要结果是测试模型,它描述了系统是怎样被测试的。测试模型包括:
测试用例,规定了在系统重要测试什么。
测试规程,规定了怎样执行测试用例。
测试构件,使测试规程自动化。
测试还产生测试计划、对已执行的测试的评估以及可以向其他核心工作流(如设计和实现工作流)缺陷的反馈。
西安交通大学 刘海岩 35
习题
1、面向对象的测试有哪些特点?
2、面向对象软件在单元测试、集成测试、确认测试等方面有什么样的测试策略?与传统方法的测试策略相比有哪些扩充?
3、对软件进行单元测试和集成测试时,如何设计测试用例?
4,RUP的测试活动有哪些?请对各个活动的特点作总结。