面向对象软件的测试策略
考试要求: 掌握     
知识路径:  > 测试技术的分类  > 面向对象的软件测试技术


本知识点历年真题试卷分布
>> 试题列表    
 

 
       面向对象分析(OOA)的测试
       传统的面向过程分析是一个功能分解的过程,是把一个系统看成可以分解的功能的集合。这种传统的功能分解分析法的着眼点在于,一个系统需要什么样的信息处理方法和过程,以过程的抽象来对待系统的需要。而面向对象分析(OOA)是“把E-R图和语义网络模型,即信息造型中的概念,与面向对象程序设计语言中的重要概念结合在一起而形成的分析方法”,最后通常是以问题空间的图表的形式进行描述。
       OOA直接映射问题空间,全面地将问题空间中实现功能的现实抽象化。将问题空间中的实例抽象为对象(不同于C++中的对象概念),用对象的结构反映问题空间的复杂实例和复杂关系,用属性和服务表示实例的特性和行为。对一个系统而言,与传统分析方法产生的结果相反,行为是相对稳定的,结构是相对不稳定的,这更充分反映了现实的特性。OOA的结果是为后面阶段类的选定和实现,类层次结构的组织和实现提供平台。因此,OOA对问题空间分析抽象的不完整,最终会影响软件的功能实现,导致软件开发后期产生大量原本可避免的修补工作;另外,一些冗余的对象或结构会影响类的选定、程序的整体结构或增加程序员不必要的工作量。因此,对OOA测试的重点在其完整性和冗余性。
       尽管OOA的测试是一个不可分割的系统过程,为叙述的方便,鉴于Coad方法所提出的OOA实现步骤,对OOA阶段的测试划分为以下五个方面:
       . 对认定的对象的测试。
       . 对认定的结构的测试。
       . 对认定的主题的测试。
       . 对定义的属性和实例关联的测试。
       . 对定义的服务和消息关联的测试。
          对认定的对象的测试
          OOA中认定的对象是对问题空间中的结构,其他系统、设备,被记忆的事件,系统涉及的人员等实际实例的抽象。对它的测试可以从如下方面考虑:
          . 认定的对象是否全面,是否问题空间中所有涉及到的实例都反映在认定的抽象对象中了。
          . 认定的对象是否具有多个属性。只有一个属性的对象通常应看成其他对象的属性,而不是抽象为独立的对象。
          . 对认定为同一对象的实例是否有共同的、区别于其他实例的共同属性。
          . 对认定为同一对象的实例是否提供或需要相同的服务,如果服务随着实例的不同而变化,认定的对象就需要分解或利用继承性来分类表示。
          . 如果系统没有必要始终保持对象代表的实例的信息,提供或者得到关于它的服务、认定的对象也无必要。
          . 认定的对象的名称应该尽量准确、适用。
          对认定的结构的测试
          在Coad方法中,认定的结构指的是多种对象的组织方式,用来反映问题空间中的复杂实例和复杂关系。认定的结构分为两种:分类结构和组装结构。分类结构体现了问题空间中实例的一般与特殊的关系,组装结构体现了问题空间中实例整体与局部的关系。
          . 对认定的分类结构的测试可从如下方面着手:
          ①对于结构中的一种对象,尤其是处于高层的对象,是否在问题空间中含有不同于下一层对象的特殊的可能性,即是否能派生出下一层对象。
          ②对于结构中的一种对象,尤其是处于同一低层的对象,是否能抽象出在现实中有意义的更一般的上层对象。
          ③对所有认定的对象,是否能在问题空间内向上层抽象出在现实中有意义的对象。
          ④高层的对象的特性是否完全体现下层的共性。
          ⑤低层的对象是否有高层特性基础上的特殊性。
          . 对认定的组装结构的测试从如下方面入手:
          ①整体(对象)和部件(对象)的组装关系是否符合现实的关系。
          ②整体(对象)的部件(对象)是否在考虑的问题空间中有实际应用。
          ③整体(对象)中是否遗漏了反映在问题空间中有用的部件(对象)。
          ④部件(对象)是否能够在问题空间中组装新的有现实意义的整体(对象)。
          对认定的主题的测试
          主题是在对象和结构的基础上更高一层的抽象,是为了提供OOA分析结果的可见性,如同文章对各部分内容的概要。对主题层的测试应该考虑以下方面:
          . 贯彻George Miller的“7+2”原则(George Miller指出普通人的短期记忆的能量,是7加2或减2,即5与9之间)。如果主题个数超过7个,就要求对有较密切属性和服务的主题进行归并。
          . 主题所反映的一组对象和结构是否具有相同和相近的属性和服务。
          . 认定的主题是否是对象和结构更高层的抽象,是否便于理解OOA结果的概貌(尤其是对非技术人员的OOA结果读者)。
          . 主题间的消息联系(抽象)是否代表了主题所反映的对象和结构之间的所有关联。
          对定义的属性和实例关联的测试
          属性是用来描述对象或结构所反映的实例的特性。而实例关联是反映实例集合间的映射关系。对属性和实例关联的测试从如下方面考虑:
          . 定义的属性是否对相应的对象和分类结构的每个现实实例都适用。
          . 定义的属性在现实世界是否与这种实例关系密切。
          . 定义的属性在问题空间是否与这种实例关系密切。
          . 定义的属性是否能够不依赖于其他属性被独立理解。
          . 定义的属性在分类结构中的位置是否恰当,低层对象的共有属性是否在上层对象属性体现。
          . 在问题空间中每个对象的属性是否定义完整。
          . 定义的实例关联是否符合现实。
          . 在问题空间中实例关联是否定义完整,特别需要注意“一对多”和“多对多”的实例关联。
          对定义的服务和消息关联的测试
          定义的服务,就是定义的每一种对象和结构在问题空间所要求的行为。由于问题空间中实例间必要的通信,在OOA中需要相应地定义消息关联。对定义的服务和消息关联的测试从如下方面进行:
          . 对象和结构在问题空间的不同状态是否定义了相应的服务。
          . 对象或结构所需要的服务是否都定义了相应的消息关联。
          . 定义的消息关联所指引的服务提供是否正确。
          . 沿着消息关联执行的线程是否合理,是否符合现实过程。
          . 定义的服务是否重复,是否定义了能够得到的服务。
       面向对象设计(OOD)的测试
       在以往结构化的设计方法,用的“是面向作业的设计方法,它把系统分解以后,提出一组作业,这些作业是以过程实现系统的基础构造,把问题域的分析转化为求解域的设计,分析的结果是设计阶段的输入”。
       而面向对象设计(OOD)采用“造型的观点”,以OOA为基础归纳出类,并建立类结构或进一步构造成类库,以实现分析结果对问题空间的抽象。OOD归纳的类既可以是对象简单的延续,也可以是不同对象的相同或相似的服务。因此,OOD是OOA的进一步细化和更高层的抽象。所以,OOD与OOA的界限一般是很难严格区分的。OOD确定类和类结构不仅是满足当前需求分析的要求,更重要的是通过重新组合或加以适当的补充,能方便实现功能的重用和扩增,以不断适应用户的要求。因此,对OOD的测试,建议针对功能的实现和重用以及对OOA结果的拓展,从如下三方面考虑:
       . 对认定的类的测试。
       . 对构造的类层次结构的测试。
       . 对类库的支持的测试。
          对认定的类的测试
          OOD认定的类可以是OOA中认定的对象,也可以是对象所需要的服务的抽象,对象所具有的属性的抽象。认定的类原则上应该尽量具有基础性,这样才便于维护和重用。测试认定的类包括如下方面:
          . 是否涵盖了OOA中所有认定的对象。
          . 是否能体现OOA中定义的属性。
          . 是否能实现OOA中定义的服务。
          . 是否对应着一个含义明确的数据抽象。
          . 是否尽可能少地依赖其他类。
          . 类中的方法(在C++中即类的成员函数)是否是单用途。
          对构造的类层次结构的测试
          为了能够充分发挥面向对象的继承共享特性,OOD的类层次结构,通常基于OOA中产生的分类结构的原则来组织,着重体现父类和子类间的一般性和特殊性。在当前的问题空间,对类层次结构的主要要求是能在解空间构造实现全部功能的结构框架。为此,需要测试如下方面:
          . 类层次结构是否涵盖了所有定义的类。
          . 是否能体现OOA中所定义的实例关联。
          . 是否能实现OOA中所定义的消息关联。
          . 子类是否具有父类没有的新特性。
          . 子类间的共同特性是否完全在父类中得以体现。
          对类库支持的测试
          对类库的支持虽然也属于类层次结构的组织问题,但其强调的重点是再次软件开发的重用。由于它并不直接影响当前软件的开发和功能实现,因此,将其单独提出来测试,也可作为对高质量类层次结构的评估。拟定测试点如下:
          . 一组子类中关于某种含义相同或基本相同的操作,是否有相同的接口(包括名字和参数表)。
          . 类中方法(在C++中即类的成员函数)的功能是否较单纯,相应的代码行是否较少(建议为不超过30行)。
          . 类的层次结构是否是深度大,宽度小的。
          因为OOA、OOD阶段所建立的分析和设计模型不能进行传统意义上的测试,它们不能被执行,所以在每次迭代之后,一定要进行评审。评审主要针对以下两个方面。
          . 正确性
          OO开发模式为演化(重复迭代)性质,即系统的初期为非形式化表示,以后发展为类的细节模型、类的连接和关联,系统设计和配置,以及对类的设计(通过消息组成对象连接模型)。每一阶段都要进行评审。
          正确性主要在于分析和设计模型表示所使用的符号语法是否正确,语义是否正确(即模型与真实世界领域是否一致),以及类的关联(实例间的联系)是否正确地反映了真实世界对象间的关联。
          . 一致性
          由于演化性质,OOA和OOD模型(包括分析、设计和编码层次,即类、属性、操作、消息)不仅要正确,而且要一致。一致性可以用模型内各实体间的关联性来判断。
          为评估一致性,应检查每个类及其与其他类的连接。可运用类-责任-协作者(CRC)模型和对象-关系图。CRC模型由CRC索引卡片构成,每个CRC卡片列出类名、类的责任(操作)、以及其协作者类,类向它们发送消息并依赖它们完成自己的责任。协作包含了在OO系统的类之间的一系列关系(即连接),对象关系模型提供了类之间连接的图形表示。所有这些信息可以从OOA模型得到。
          为评估类模型,推荐采用以下步骤:
          ①再次考察CRC模型和对象-关系模型,进行交叉检查,以保证由OOA模型所蕴含的协作适当地反应在二者中。
          ②检查每个CRC索引卡片的描述,以确定是否某被授权的责任是协作者定义的一部分,例如,某个POS结账系统定义的类,称为credit sale,该类的CRC卡片如下图所示。
          
          一个用于复审的CRC卡片例子
          对于这组类和协作,我们问如果某责任(如,read credit card)被委托给指定的协作者(credit card),该责任是否将被完成,即,类credit card是否具有一个操作以使得它可以被读。在本例的情形中,我们的回答是:“是”。遍历对象关系,以保证所有这样的连接是有效的。
          ③反转该连接,以保证每个被请求服务的协作者正在接收来自合理源的请求,例如,如果credit card类接收来自credit sale类的purchase amount请求,则将会有问题,因为credit card并不知道purchase amount。
          ④使用在第③步检查的反转连接,确定是否可能需要其他的类,或责任是否被合适地在类间分组。
          ⑤确定是否被广泛请求的责任可被组合为单个的责任,例如,read credit card和get authorization在每种情况均发生,它们可以被组合为validate credit。request责任,它结合了获取信用卡号及获得授权。
          ⑥步骤①~⑤被迭代地应用到每个类,并贯穿于OOA模型的每次演化过程中。
          一旦已经创建了设计模型,也应该进行对系统设计和对象设计的复审。系统设计描述了构成产品的子系统、子系统被分配到处理器的方式,以及类到子系统的分配。对象模型表示了每个类的细节和实现类间的协作所必须的消息序列活动。
          通过检查在OOA阶段开发的对象-行为模型,和映射需要的系统行为到被设计用于完成该行为的子系统来进行系统设计的复审。也在系统行为的语境内复审并发性和任务分配,评估系统的行为状态以确定哪些行为并发地存在。
          对象模型应该针对对象-关系网络来测试,以保证所有设计对象包含为实现每张CRC索引卡片定义的协作所必须的属性和操作。
       面向对象编程(OOP)的测试
       面向对象程序所具有的继承、封装和多态的新特性,使得传统的测试策略不再完全适用。封装是对数据的隐藏,外界只能通过被提供的操作来访问或修改数据,这样降低了数据被任意修改和读写的可能性,降低了传统程序中对数据非法操作的测试。继承是面向对象程序的重要特点,继承使得代码的重用率提高,同时也使错误传播的概率提高。继承使得传统测试遇到了这样一个难题:对继承的代码究竟应该怎样测试?(参见面向对象单元测试)。多态使得面向对象程序对外呈现出强大的处理能力,但同时却使得程序内“同一”函数的行为复杂化,测试时不得不考虑不同类型具体执行的代码和产生的行为。
       面向对象程序是把功能的实现分布在类中。能正确实现功能的类,通过消息传递来协同实现设计要求的功能。正是这种面向对象程序风格,将出现的错误能精确地确定在某一具体的类。因此,在面向对象编程(OOP)阶段,忽略类功能实现的细则,将测试的目光集中在类功能的实现和相应的面向对象程序风格上,主要体现为以下两个方面(假设编程使用C++语言)。
       . 数据成员是否满足数据封装的要求。
       . 类是否实现了要求的功能。
          数据成员是否满足数据封装的要求
          数据封装是数据和数据有关的操作的集合。检查数据成员是否满足数据封装的要求,基本原则是数据成员是否被外界(数据成员所属的类或子类以外的调用)直接调用。更直观地说,当改变数据成员的结构时,是否影响了类的对外接口,是否会导致相应的外界必须改动。值得注意的是,有时强制的类型转换会破坏数据的封装特性。例如:
          
          在上面的程序段中,xx的私有数据成员a和b可以通过yy被随意访问。
          类是否实现了要求的功能
          类所实现的功能都是通过类的成员函数执行的。在测试类的功能实现时,应该首先保证类成员函数的正确性。单独地看待类的成员函数,与面向过程程序中的函数或过程没有本质的区别,几乎所有传统的单元测试中所使用的方法,都可在面向对象的单元测试中使用。具体的测试方法在面向对象的单元测试中介绍。类函数成员的正确行为只是类能够实现要求的功能的基础,类成员函数间的作用和类之间的服务调用是单元测试无法确定的。因此,需要进行面向对象的集成测试。具体的测试方法将在面向对象的集成测试中介绍。需要着重声明的是,测试类的功能,不能仅满足于代码能无错地运行或被测试类所提供的功能无错,应该以所做的OOD结果为依据,检测类提供的功能是否满足设计的要求,是否有缺陷。必要时还应该参照OOA的结果,以之为最终标准。
       面向对象软件的单元测试
       传统的单元测试是针对程序的函数、过程或完成某一特定功能的程序块,可沿用单元测试的概念,来实际测试类成员函数。一些传统的测试方法在面向对象的单元测试中都可以使用。如等价类划分法、因果图法、边值分析法、逻辑覆盖法、路径分析法、程序插装法等。单元测试一般建议由程序员完成。
       用于单元级测试的测试分析(提出相应的测试要求)和测试用例(选择适当的输入,达到测试要求),规模和难度等均远小于后面将介绍的对整个系统的测试分析和测试用例,而且强调对语句应该有100%的执行代码覆盖率。在设计测试用例选择输入数据时,可以基于以下两个假设(假设使用C++编程语言):
       . 如果函数(程序)对某一类输入中的一个数据正确执行,对同类中的其他输入也能正确执行。
       . 如果函数(程序)对某一复杂度的输入正确执行,对更高复杂度的输入也能正确执行。例如,需要选择字符串作为输入时,基于本假设,就无须计较字符串的长度。除非字符串的长度是要求固定的,如IP地址字符串。
       在面向对象程序中,类成员函数通常都很小,功能单一,函数之间调用频繁,容易出现一些不宜发现的错误。例如:
       
       该语句没有全面检查write()的返回值,无意中只断然假设了数据被完全写入和没有写入两种情况。当测试也忽略了数据部分的写入情况时,就给程序遗留了隐患,建议加入else语句。
       . 按程序的设计,使用函数strrchr()查找最后的匹配字符,但错误程序中写成了函数strchr(),使程序功能实现时查找的是第一个匹配字符。
       . 程序中将if(strncmp(str1,str2,strlen(str1)))误写成了if(strncmp(str1,str2,strlen (str2)))。如果测试用例中使用的数据str1和str2长度一样,就无法检测出来。
       因此,在做测试分析和设计测试用例时,应该注意面向对象程序的这个特点,仔细地进行测试分析和设计测试用例,尤其是针对以函数返回值作为条件判断选择,字符串操作等情况。
       面向对象编程的特性使得对成员函数的测试,不完全等同于传统的函数或过程测试。尤其是继承特性和多态特性,使子类继承或过载的父类成员函数出现了传统测试中未遇见的问题。Brian Marick给出了两方面的考虑如下:
       . 继承的成员函数是否都不需要测试。
       对父类中已经测试过的成员函数,有两种情况需要在子类中重新测试:
       ①继承的成员函数在子类中做了改动;②成员函数调用了改动过的成员函数的部分。例如:假设父类Bass有两个成员函数:Inherited()和Redefined(),子类Derived只对Redefined()做了改动。Derived::Redefined()显然需要重新测试。对于Derived::Inherited(),如果它有调用Redefined()的语句(如:x=x/Redefined()),就需要重新测试,反之,无此必要。
       . 对父类的测试是否能照搬到子类。
       延用上面的假设,Base::Redefined()和Derived::Redefined()已经是不同的成员函数,它们有不同的服务说明和执行。对此,照理应该对Derived::Redefined()重新进行测试分析,设计测试用例。但由于面向对象的继承使得两个函数有相似之处,故只需在Base::Redefined()的测试要求和测试用例上添加对Derived::Redfined()的新的测试要求和增补相应的测试用例。例如:Base::Redefined()函数中含有如下语句
       
       在原有的测试上,对Derived::Redfined()的测试只需做如下改动:将value==0的测试结果期望改动;增加value==99的测试。
       多态有几种不同的形式,如参数多态、包含多态、过载多态。包含多态和过载多态在面向对象语言中通常体现在子类与父类的继承关系上,对这两种多态的测试参见上述对父类成员函数继承和过载的论述。包含多态虽然使成员函数的参数可有多种类型,但通常只是增加了测试的复杂度。对具有包含多态的成员函数进行测试时,只需要在原有的测试分析和基础上增加对测试用例中输入数据的类型的考虑。
       面向对象软件的集成测试
       因为面向对象软件没有层次的控制结构,传统的自顶向下和自底向上集成策略就没有意义,而且,一次集成一个操作到类中(传统的增量集成方法)经常是不可能的,这是由于“构成类的成分的直接和间接的交互”。
       此外,传统的自底向上通过集成完成的功能模块测试,一般可以在部分程序编译完成的情况下进行。而对于面向对象程序,相互调用的功能是散布在程序的不同类中的,类通过消息相互作用申请和提供服务。类的行为与它的状态密切相关,状态不仅仅是体现在类数据成员的值,也许还包括其他类中的状态信息。由此可见,类之间的相互依赖极其紧密,根本无法在编译不完全的程序上对类进行测试。所以,面向对象的集成测试通常需要在整个程序编译完成后进行。此外,面向对象程序具有动态特性,程序的控制流往往无法确定,因此,也只能对整个编译后的程序做基于黑盒子的集成测试。
       对OO软件的集成测试有两种不同的策略,第一种称为基于线程的测试(thread based testing),集成对回应系统的一个输入或事件所需的一组类,每个线程被集成并分别测试,应用回归测试以保证没有产生副作用。第二种称为基于使用的测试(use based testing),通过测试那些几乎不使用服务器类的类(称为独立类)而开始构造系统,在独立类测试完成后,下一层中使用独立类的类(称为依赖类)被测试。这个依赖类层次的测试序列一直持续到构造完整个系统。序列和传统集成不同,使用驱动器和桩(stubs)作为替代操作是要尽可能避免的。
       面向对象的集成测试,能够检测出相对独立的,单元测试无法检测出的,那些类相互作用时才会产生的错误。基于单元测试对成员函数行为正确性的保证,集成测试只关注于系统的结构和内部的相互作用。面向对象的集成测试可以分成两步进行:先进行静态测试,再进行动态测试。
       静态测试主要针对程序的结构进行,检测程序结构是否符合设计要求。现在流行的一些测试软件都能提供一种称为“可逆性工程”的功能,即通过源程序得到类关系图和函数功能调用关系图,例如International Software Automation公司的Panorama-2 for Windows 95、Rational公司的Rose C++Analyzer等,将“可逆性工程”得到的结果与OOD的结果相比较,检测程序结构和实现上是否有缺陷。换句话说,通过这种方法检测OOP是否达到了设计要求。
       动态测试设计测试用例时,通常需要上述的功能调用结构图、类关系图或者实体关系图为参考,确定不需要被重复测试的部分,从而优化测试用例,减少测试工作量,使得进行的测试能够达到一定覆盖标准。测试所要达到的覆盖标准可以是:达到类所有的服务要求或服务提供的一定覆盖率;依据类间传递的消息,达到对所有执行线程的一定覆盖率;达到类的所有状态的一定覆盖率等。同时也可以考虑使用现有的一些测试工具来得到程序代码执行的覆盖率。
       具体设计测试用例,可参考下列步骤。
       ①先选定检测的类,参考OOD分析结果,仔细确定出类的状态和相应的行为,类或成员函数间传递的消息,输入或输出的界定等。
       ②确定覆盖标准。
       ③利用结构关系图确定待测类的所有关联。
       ④根据程序中类的对象构造测试用例,确认使用什么输入激发类的状态,使用类的服务和期望产生什么行为等。
       值得注意的是,设计测试用例时,不但要设计确认类功能满足的输入,还应该有意识地设计一些被禁止的例子,确认类是否有不合法的行为产生,如发送与类状态不相适应的消息,要求不相适应的服务等。根据具体情况,动态的集成测试,有时也可以通过系统测试完成。
       面向对象软件的确认和系统测试
       通过单元测试和集成测试,仅能保证软件开发的功能得以实现,但不能确认在实际运行时,它是否能够满足用户的需要,是否大量地存在着实际使用条件下会被诱发产生错误的隐患。为此,对完成开发的软件必须进行规范的系统测试。即需要测试它与系统其他部分配套运行的表现,以确保在系统各部分协调工作的环境下软件也能正常运行。
       系统测试应该尽量搭建与用户实际使用环境相同的测试平台,应该保证被测系统的完整性,对暂时没有的系统设备部件,应采取相应的模拟手段。系统测试时,应该参考OOA分析的结果,对应描述的对象、属性和各种服务,检测软件是否能够完全“再现”问题空间。系统测试不仅是检测软件的整体行为表现,从另一个侧面看,也是对软件开发设计的再确认。
       在系统层次,类连接的细节消失了。和传统有效性测试一样,OO软件的有效性集中在用户可见的动作和用户可识别的系统输出上。为了协助有效性测试的导出,测试人员应该利用作为分析模型一部分的使用实例,使用实例提供了在用户交互需求中很可能发现错误的一个场景。传统的黑盒测试方法可被用于驱动有效性测试,此外,测试用例可以从对象-行为模型和作为OOA的一部分的事件流图中导出。系统测试需要对被测的软件结合需求分析做仔细的测试分析,建立测试用例。
       OO软件确认和系统测试具体的测试内容与传统系统测试基本相同,包括:功能测试、强度测试、性能测试、安全测试、恢复测试、易用性测试、安装/卸载测试(install/uninstalltest)等,此处不再赘述。
 

更多复习资料
请登录电脑版软考在线 www.rkpass.cn

京B2-20210865 | 京ICP备2020040059号-5
京公网安备 11010502032051号 | 营业执照
 Copyright ©2000-2025 All Rights Reserved
软考在线版权所有