面向技术所独有的多态、继承、封装等新特点,使OO程序设计比传统语言程序设计产生错误的可能性增大,使得传统软件测试中的重点不再显得那么突出,也使原来测试经验和实践证明的次要方面成为了主要问题。

    使用低质量的软件,运行过程中会产生各种各样的问题,可能带来不同程度的严重后果,轻者影响系统的正常工作,重者造成事故和财产损失。软件测试是保证软件质量的重要的手段,它使用人工或自动手段来运行或测定某个系统的过程,其目的在于检验它是否满足规定的需求,弄清预期结果与实际结果之间的差别。

    面向对象技术是一种全新的软件开发技术,正逐渐代替被广泛使用的面向过程的开发方法,被看成是解决软件危机的新兴技术。尽管面向对象技术的基本思想保证了软件应该有更高的质量,但实际情况却并非如此,因为无论采用什么样的编程技术,编程人员的错误都是不可避免的,而且由于面向对象技术开发的软件代码重用率高,更需要严格测试,以避免错误的繁衍。

    一、评审

    因为OOA、OOD阶段所的OOA和OOD模型不能执行,所以在每次迭代之后,一定要进行评审。

    1.正确性

    OO开发模式为演化(重复迭代)性质,即系统的初期为非形式化表示,以后发展为类的细节模型、类的连接和关联,系统设计和配置,以及对类的设计(通过消息组成对象连接模型)。每一阶段都要进行评审。

    正确性主要在分析和设计模型表示所使用的符号语法是否正确,语义是否正确(即模型与真实世界领域是否一致),以及类的关联(实例间的联系)是否正确地反映了真实世界对象间的关联。

    2.一致性

    由于演化性质,OOA和OOD模型(包括分析、设计和编码层次,即类、属性、操作、消息)不仅要正确,而且要一致。一致性可以用模型内各实体间的关联性来判断。

    二、测试

    1.单元测试

    OOP完成以后,可以进行单元测试了。与传统的单元不同,OO中的单元是类。每个类都封装了属性和管理这些数据的操作。一个类可以包含许多不同的操作,一个特殊的操作可以出现在许多不同的类中。

    传统的单元测试只能测试一个操作(功能)。而在OO单元测试中,一个操作功能只能作为一个类的一部分,类中有多个操作(功能),要进行多个操作的测试。

    另外,父类中定义的某个操作被许多子类继承。但在实际应用中,不同子类中某个操作在使用时又有细微的不同,所以还必须对每个子类中某个操作进行测试。

    类的测试可以使用多种方法,如基于故障的测试、随机测试和分割测试等。每一种方法都要检查封装在类中的操作,即设计的测试序列(用例),要保证相关的操作被检查。因为类的属性值表示类的状态,由此来确定被检查的错误是否存在。

    2.组装测试

    传统软件的层次模块间存在着控制关系,而OO软件没有层次控制结构。所以传统的自顶向下和自底向上的组装策略在OO软件组装测试中没有意义了。

    另外,一个类每次组装一个操作(传统软件的增量法)在OO软件组装中是不够的,因为组成类的各个成分之间存在着直接或间接的交互作用。OO软件的组装测试有两种不同的策略:

    (1)基于线程测试(thread-based-testing) 基于线程的测试是把合作对应一个输入或事件的类集合组装起来,也是用响应系统的一个输入或一个事件的请求来组装类的集合。对每个线程都要分别进行组装和测试。

    (2)基于使用测试(use-based-testing) 基于使用的测试是按分层来组装系统,可以先进行独立类的测试。在独立类测试之后,下一个类的层次叫从属类。从属类用独立类进行测试。这种从属类层的顺序测试直到整个系统被构造完成。传统软件使用驱动程序和连接程序作为置换操作,而OO软件一般不用。

    OO系统组装时还必须进行类间合作(强调上下级关系)的测试。类的合作测试与单个类测试相似,可用随机应用和分割测试来完成。另外,还可以用基于脚本测试和行为模型导出的测试进行。

    3.确认测试

    确认测试是在系统层进行测试,因此类间的联系细节出现了。与传统软件一样,OO软件确认测试也主要集中在用户可见活动和用户可识别的系统输出上,所以OO软件也使用传统软件的黑盒子测试方法。确认测试大多使用基于脚本(scenarios)的测试,因而使用用例成为确认测试的主要驱动器。

    三、测试用例设计

    这种设计目前正处于形成阶段。

    传统软件测试用例设计是从软件的各个模块的算法细节得出的,而OO软件测试用例则着眼于适当的操作序列,以实现对类的说明。

    黑盒子测试不仅适用于传统软件,也适用OO软件测试。白盒子测试也用于OO软件类的操作定义。但OO软件中许多类的操作结构简明,所以有人认为在类层上测试可能要比传统软件中的白盒子测试方便。

    OO测试用例设计包含OO概念,在OO度量中所讲的五个特性:局域性、封装性、信息隐藏、继承性和对象的抽象,肯定会对用例设计带来额外的麻烦和困难。

    Berard提出了一些测试用例的设计方法,主要原则包括:

    (1)每个测试用例应当给予特殊的标识,并且还应当与测试的类有明确的联系。

    (2)测试目的应当明确。

    (3)应当为每个测试用例开发一个测试步骤列表。这个列表应包含以下一些内容:

    列出所要测试对象的专门说明。

    列出将要作为测试结果运行的消息和操作。

    列出测试对象可能发生的例外情况。

    列出外部条件(即为了正确对软件进行测试所必须有的外部环境的变化)。

    列出为了帮助理解和实现测试所需要的附加信息。
    1.基于故障的测试

    在OO软件中,基于故障的测试具有较高的发现可能故障的能力。由于系统必须满足用户的需求,因此,基于故障的测试要从分析模型开始,考察可能发生的故障。为了确定这些故障是否存在,可设计用例去执行设计或代码。

    基于故障测试的关键取决于测试设计者如何理解“可能的错误”。而在实际中,要求设计者做到这点是不可能的。

    基于故障测试也可以用于组装测试,组装测试可以发现消息联系中“可能的故障”。

    “可能的故障”一般为意料之外的结果、错误地使用了操作/消息、不正确引用等。为了确定由操作(功能)引起的可能故障必须检查操作的行为。

    这种方法除用于操作测试外,还可用于属性测试,用以确定其对于不同类型的对象行为是否赋予了正确的属性值。因为一个对象的“属性”是由其赋予属性的值定义的。

    应当指出,组装测试是从客户对象(主动),而不是从服务器对象(被动)上发现错误。正如传统的软件组装测试是把注意点集中在调用代码而不是被调用代码一样,即发现客户对象中“可能的故障”。

    2.基于脚本的测试

    基于故障测试减少了两种主要类型的错误:

    (1)不正确的规格说明,如做了用户不需要的功能,也可能缺少了用户需要的功能。

    (2)子系统间的交互作用没有考虑,如一个子系统(事件或数据流等)的建立,导致其他子系统的失败。

    基于脚本的测试主要关注用户需要做什么,而不是产品能做什么,即从用户任务(使用用例)中找出用户要做什么及去执行。

    这种基于脚本的测试有助于在一个单元测试情况下检查多重系统。所以基于脚本测试用例测试比基于故障测试不仅更实际(接近用户),而且更复杂一点。

    例如:考察一个文本编辑的基于脚本测试的用例设计。

    使用用例:确定终设计

    背景:打印终设计,并能从屏幕图像上发现一些不易见到的且让人烦恼的错误。

    其执行事件序列:打印整个文件;移动文件,修改某些页;当某页被修改,打印某页;有时要打印许多页。

    显然,测试者希望发现打印和编辑两个软件功能是否能够相互依赖,否则会产生错误。

    3.OO类的随机测试

    如果一个类有多个操作(功能),这些操作(功能)序列有多种排列。而这种不变化的操作序列可随机产生,用这种可随机排列的序列来检查不同类实例的生存史,叫随机测试。

    例如一个银行信用卡的应用,其中有一个类:计算(account)。该account的操作有:open、setup、deposit、withdraw、balance、summarize、creditlimit和close。

    这些操作中的每一项都可用于计算,但open、close必须在其他计算的任何一个操作前后执行,即使open和close有这种限制,这些操作仍有多种排列。所以一个不同变化的操作序列可由于应用不同而随机产生,如一个Account实例的小行为生存史可包括以下操作:

    open+setup+deposit+[deposit|withdraw |balance|summarize|creditlimit]+withdraw+close

    从此可见,尽管这个操作序列是小测试序列,但在这个序列内仍可以发生许多其他的行为。

    4.类层次的分割测试

    这种测试可以减少用完全相同的方式检查类测试用例的数目。这很像传统软件测试中的等价类划分测试。分割测试又可分三种。

    (1)基于状态的分割 按类操作是否改变类的状态来分割(归类)。这里仍用account类为例,改变状态的操作有deposit、withdraw,不改变状态的操作有balance、 summarize、creditlimit。如果测试按检查类操作是否改变类状态来设计,则结果如下:

    用例1:执行操作改变状态

    open+setup+deposit+deposit+withdraw+withdraw+close。

    用例2:执行操作不改变状态

    open+setup+deposit+summarize+creditlimit+withdraw+close。

    (2)基于属性的分割 按类操作所用到的属性来分割(归类),如果仍以一个account类为例,其属性creditlimit能被分割为三种操作:用creditlimit的操作,修改creditlimit的操作,不用也不修改creditlimit的操作。

    这样,测试序列可按每种分割来设计。

    (3)基于类型的分割 按完成的功能分割(归类)。例如,在account类的操作中,可以分割为:初始操作open、setup;计算操作deposit、withdraw;查询操作balance、summarize、creditlimit;终止操作close。

    5.由行为模型(状态、活动、顺序和合作图)导出的测试

    状态转换图(STD)可以用来帮助导出类的动态行为的测试序列,以及这些类与之合作的类的动态行为测试序列。

    为了说明问题,仍用前面讨论过的account类。开始由empty acct状态转换为setup acct状态。类实例的大多数行为发生在working acct状态中。而后,取款和关闭分别使account类转换到non-working acct和dead acct状态。

    这样,设计的测试用例应当是完成所有的状态转换。换句话说,操作序列应当能导致account类所有允许的状态进行转换。

    测试用例:

    open+setupAcct+deposit(initial)+withdraw(final)+close

    还可导出更多的测试用例,以保证该类所有行为被充分检查。

    小资料

    OO软件测试的主要目标与传统软件测试一样,即用小量的投入来大限度地发现软件存在的错误。但由于OO软件具有的特殊性质,OO软件测试在内容、策略和方法上与传统软件测试不完全相同。

    (1)OOA(Object-Oriented Analysis)和OOD(Object-Oriented Design)的评审与传统软件的分析和设计相同,应给出相应的评审检查表。

    (2)OOP(Object-Oriented Programming)后,单元和组装测试策略必须做相应的改变。

    (3)测试用例设计必须说明软件特有的性质。