第11章 · 原文阅读

测试评审方法

第11章 测试评审方法

软件测试与评审是软件质量保证的主要手段之一,也是在将软件交付给客户之前所必须完成的步骤。目前,软件的正确性证明尚未得到根本的解决,软件测试与评审仍是发现软件错误(缺陷)的主要手段。

本章重点要求读者掌握测试方法、评审方法、验证与确认、测试自动化、面向对象的测试等 5 个方面的知识。

11.1 测试方法

在介绍软件测试之前,首先应该明确“错误”(error)和“缺陷”(fault)的概念。根据IEEE 的定义,“错误”主要针对软件开发过程,“缺陷”主要针对软件产品。软件开发人员在软件开发过程(主要是分析、设计和编码过程)中所出现的“错误”是导致软件产品“缺陷”的原因,反过来说,“缺陷”是“错误”的结果和表现形式。

软件测试的目的就是在软件投入生产性运行之前,尽可能多地发现软件产品(主要是指程序)中的错误(缺陷)。

为了发现软件中的错误(缺陷),应竭力设计能暴露错误(缺陷)的测试用例。测试用例是由测试数据和预期结果构成的。一个好的测试用例是极有可能发现至今为止尚未发现的错误(缺陷)的测试用例。一次成功的测试是发现了至今为止尚未发现的错误(缺陷)的测试。

高效的测试是指用少量的测试用例,发现被测软件尽可能多的错误(缺陷)。

软件测试所追求的目标就是以尽可能少的时间和人力发现软件产品中尽可能多的错误(缺陷)。

11.1.1 软件测试阶段

从测试阶段上分,软件测试通常可分为单元测试、集成测试和系统测试。

1. 单元测试

单元测试(unit testing),也称模块测试,通常可放在编程阶段,由程序员对自己编写的模块自行测试,检查模块是否实现了详细设计说明书中规定的功能和算法。单元测试主要发现编程和详细设计中产生的错误,单元测试计划应该在详细设计阶段制定。

单元测试期间着重从以下几个方面对模块进行测试:模块接口、局部数据结构、重要的执行通路、出错处理通路和边界条件等。

测试一个模块时需要为该模块编写一个驱动模块和若干个桩(stub)模块。驱动模块用来调用被测模块,它接收测试者提供的测试数据,并把这些数据传送给被测模块,然后从被测模块接收测试结果,并以某种可以看见的方式(例如显示或打印)将测试结果返回给测试者。桩模块用来模拟被测模块所调用的子模块,它接受被测模块的调用,检验调用参数,并以尽可能简单的操作模拟被调用的子程序模块功能,把结果送回被测模块。顶层模块测试时不需要驱动模块,底层模块测试时不需要桩模块。

模块的内聚程度高可以简化单元测试过程。如果每个模块只完成一种功能,则需要的测试方案数目将明显减少,模块中的错误也更容易预测和发现。

2. 集成测试

集成测试(integration testing),也称组装测试,它是对由各模块组装而成的程序进行测试,主要目标是发现模块间的接口和通信问题。例如,数据穿过接口可能丢失,一个模块对另一个模块可能由于疏忽而造成有害影响,把子功能组合起来可能不产生预期的主功能,个别看来是可以接受的误差可能积累到不能接受的程度,全程数据结构可能有问题等。集成测试主要发现设计阶段产生的错误,集成测试计划应该在概要设计阶段制订。

集成的方式可分为非渐增式和渐增式。

非渐增式集成是先测试所有的模块,然后一下子把所有这些模块集成到一起,并把庞大的程序作为一个整体来测试。这种测试方法的出发点是可以“一步到位”,但测试者面对众多的错误现象,往往难以分清哪些是“真正的”错误,哪些是由其他错误引起的“假性错误”,诊断定位和改正错误也十分困难。非渐增式集成只适合一些非常小的软件。

渐增式集成是将单元测试和集成测试合并到一起,它根据模块结构图,按某种次序选一个尚未测试的模块,把它同已经测试好的模块组合在一起进行测试,每次增加一个模块,直到所有模块被集成在程序中。这种测试方法比较容易定位和改正错误,目前在进行集成测试时已普遍采用渐增式集成。

渐增式集成又可分为自顶向下集成和自底向上集成。自顶向下集成先测试上层模块,再测试下层模块。由于测试下层模块时它的上层模块已测试过,所以不必另外编写驱动模块。

自底向上集成先测试下层模块,再测试上层模块。同样,由于测试上层模块时它的下层模块已测试过,所以不必另外编写桩模块。这两种集成方法各有利弊,一种方法的优点恰好对应于另一种方法的缺点,实际测试时可根据软件特点及进度安排灵活选用最适当的方法,也可将两种方法混合使用。

3.系统测试

系统测试是软件测试中的最后的、最完整的测试,它是在单元测试和集成测试的基础上进行的,它从全局来考察软件系统的功能和性能要求。系统测试计划应该在需求分析阶段制订。

通常,系统测试包括确认测试和验收测试。

确认测试,主要依据软件需求说明书检查软件的功能、性能及其他特征是否与用户的需求一致。

软件配置复查是确认测试的另一项重要内容。复查的目的是保证软件配置的所有成分都已齐全,质量符合要求,文档与程序完全一致,具有完成软件维护所必需的细节。

如果一个软件是为某个客户定制的,最后还要由该客户来实施验收测试,以便确认其所有需求是否都已得到满足。由于软件系统的复杂性,在实际工作中,验收测试可能会持续到用户实际使用该软件之后的相当长的一段时间。

如果一个软件是作为产品被许多客户使用的,不可能也没必要由每个客户进行验收测试。

绝大多数软件开发商都使用被称为(Alpha)测试和(Beta)测试的过程,来发现那些看起来只有最终用户才能发现的错误。

a 测试由用户在开发者的场所进行,并且在开发者的指导下进行测试。开发者负责记录发现的错误和使用中遇到的问题。也就是说,测试是在“受控的”环境中进行的。

b 测试是在一个或多个用户的现场由该软件的最终用户实施的,开发者通常不在现场,用户负责记录发现的错误和使用中遇到的问题并把这些问题报告给开发者。也就是说,测试是在“不受控的”环境中进行的。

经过系统测试之后的软件通常就可以交付使用了。

11.1.2 白盒测试和黑盒测试

从测试方法上分,软件测试可分为白盒测试和黑盒测试。

1. 白盒测试

白盒测试,又称结构测试,主要用于单元测试阶段。它的前提是可以把程序看成装在一个透明的白箱子里,测试者完全知道程序的结构和处理算法。这种方法按照程序内部逻辑设计测试用例,检测程序中的主要执行通路是否都能按预定要求正常工作。

白盒测试根据软件的内部逻辑设计测试用例,常用的技术是逻辑覆盖,即考察用测试数据运行被测程序时对程序逻辑的覆盖程度。主要的覆盖标准有 6 种:语句覆盖、判定覆盖、条件覆盖、判定/条件覆盖、组合条件覆盖和路径覆盖。

很显然,语句覆盖是一种很弱的覆盖标准。

条件覆盖不一定包含判定覆盖,判定覆盖也不一定包含条件覆盖。

它的含义是,选取足够的测试用例,使得判定表达式中每个条件的所有可能结果至少出现一次,而且每个判定本身的所有可能结果也至少出现一次。

显然,满足条件组合覆盖的测试用例,也一定满足判定/条件覆盖。因此,条件组合覆盖是上述 5 种覆盖标准中最强的一种。然而,条件组合覆盖还不能保证程序中所有可能的路径都至少经过一次。

路径覆盖实际上考虑了程序中各种判定结果的所有可能组合,因此是一种较强的覆盖标准。但路径覆盖并未考虑判定中的条件结果的组合,并不能代替条件覆盖和条件组合覆盖。

2. 黑盒测试

黑盒测试,又称功能测试,主要用于集成测试和确认测试阶段。它把软件看作一个不透明的黑箱子,完全不考虑(或不了解)软件的内部结构和处理算法,它只检查软件功能是否能按照软件需求说明书的要求正常使用,软件是否能适当地接收输入数据并产生正确的输出信息,软件运行过程中能否保持外部信息(例如文件和数据库)的完整性等。

黑盒测试根据软件需求说明书所规定的功能来设计测试用例,它不考虑软件的内部结构和处理算法。

常用的黑盒测试技术包括等价类划分、边值分析、错误推测和因果图等。

所谓等价类就是某个输入域的集合,对于一个等价类中的输入值来说,它们揭示程序中错误的作用是等效的。也就是说,如果等价类中的一个输入数据能检测出一个错误,那么等价类中的其他输入数据也能检测出同一个错误;反之,如果等价类中的一个输入数据不能检测出某个错误,那么等价类中的其他输入数据也不能检测出这一错误(除非这个等价类的某个子集还属于另一等价类)。

如果一个等价类内的数据是符合(软件需求说明书)要求的、合理的数据,则称这个等价类为有效等价类。有效等价类主要用来检验软件是否实现了软件需求说明书中规定的功能。

如果一个等价类内的数据是不符合(软件需求说明书)要求的、不合理或非法的数据,则称这个等价类为无效等价类。无效等价类主要用来检验软件的容错性。

黑盒测试中,利用等价类划分方法设计测试用例的步骤是:

希赛教育专家提示:无效等价类是用来测试非正常的输入数据的,因此每个无效等价类都有可能查出软件中的错误,所以要为每个无效等价类设计一个测试用例。

通常,每一个等价类的边界,都应该着重测试,选取的测试数据应该恰好等于、稍小于或稍大于边界值。

将等价类划分法和边值分析法结合使用,更有可能发现软件中的错误。

11.1.3 缺陷的分类和级别

根据 IEEE 标准和 Paul C.Jorgensen 的教科书,软件测试中所发现的错误(缺陷)主要包括以下几类:

根据错误(缺陷)后果的严重程度,Beizer 将错误(缺陷)分为 10 级:

11.1.4 调试

调试又称为排错,调试与成功的测试形影相随。测试成功的标志是发现了错误。根据错误迹象确定错误的原因和准确位置并加以改正,主要依靠排错技术。

调试是一个相当艰苦的过程,究其原因除了开发人员心理方面的障碍外,还因为隐藏在程序中的错误具有下列特殊的性质:

在软件排错过程中,可能遇到大大小小、形形色色的问题,随着问题的增多,排错人员的压力也随之增大,过分的紧张致使开发人员在排除一个问题的同时又引入更多的新问题。

尽管排错不是一门好学的技术,但还是有若干行之有效的方法和策略的,常用的排错策略分为三类:

11.2 评审方法

根据 IEEE 1028 的定义,评审是对软件元素或者项目状态的一种评估手段,以确定其是否与计划的结果保持一致,并使其得到改进。

狭义的“软件评审”通常指软件文档和源程序的评审。广义的“软件评审”还包括与软件测试相结合的评审及管理评审。软件评审包括软件需求评审、概要设计评审、详细设计评审、软件验证和确认评审、功能检查、物理检查、综合检查和管理评审。

在评审过程中,以下几点值得注意:

11.3 验证与确认

验证与确认都是确定软件产品是否满足其预期要求和条件的过程。验证可适用于分析、设计、编码、测试和评审等众多的过程,而确认通常用于验收过程。

1.验证

软件项目的验证一般应包括合同验证、过程验证、需求验证、设计验证、编码验证、集成验证和文档验证。

供方具有满足需求的能力。

需求是一致的并覆盖了用户的需要。

为处理需求变更和升级问题规定了适当的规程。

规定了各方之间的接口及其合作规程与范围,包括所有权、许可权、版权和保密要求。

按照需求规定了验收准则和规程。

项目是适当的、及时的。

为项目选择的过程是适当的并满足合同要求的。

用于项目过程的标准、规程和环境是适当的。

根据合同要求为项目配备了经过培训的人员。

需求是明确的、一致的、无歧义的。

需求是可行的。

需求是可测试的。

设计是正确的,是可以实现需求的。

可以从需求导出设计,可以从设计追踪需求。

编码是正确的,可以实现设计和需求。

可以从设计导出编码,可以从编码追踪设计。

每一个软件项的软件部件和软件单元已完整、正确地集成到软件项中。

系统的硬件项、软件项和人工操作已完整、正确地集成到系统中。

文档是充分的、完备的、一致的。

文档制订是及时的。

文档配置管理遵循了规定的规程。

2. 确认

如果项目需要开展确认工作,应建立一个确认过程,以确认软件产品满足其预期用途。

确认可以是组织内部的,也可以由独立的第三方实施。

一般来讲,确认过程应包括下列任务:

11.4 测试自动化

软件测试的工作量很大,但测试却极有可能应用计算机进行相当一部分自动化的工作,原因是测试的许多操作是重复性的、非智力创造性的、需要细致注意力的工作,而计算机就最适合于代替人类去完成这些任务。测试自动化会对整个开发工作的质量、成本和周期带来非常明显的效果。

一些适于考虑进行自动化的测试工作为:

11.5 面向对象的测试

传统的软件测试策略是从“小型测试”开始,逐步走向“大型测试”。即从单元测试开始,然后进入集成测试,最后是系统测试。

面向对象程序的结构不再是传统的功能模块结构,作为一个整体,原有集成测试所要求的逐步地将开发的模块搭建在一起进行测试的方法已成为不可能。而且,面向对象软件抛弃了传统的开发模式,对每个开发阶段都有不同以往的要求和结果,已经不可能用功能细化的观点来检测面向对象分析和设计的结果。因此,传统的测试模型对面向对象软件已经不再适用。

1. 面向对象测试模型

面向对象的开发模型突破了传统的瀑布模型,将开发分为 OOA、OOD 和 OOP 三个阶段。针对这种开发模型,结合传统的测试步骤的划分,可以把面向对象的软件测试分为:面向对象分析的测试、面向对象设计的测试、面向对象编程的测试、面向对象的单元测试、面向对象的集成测试和面向对象的系统测试。

2.面向对象分析的测试

传统的面向过程分析是一个功能分解的过程,是把一个系统看成可以分解的功能的集合。

功能分解分析法的着眼点在于一个系统需要什么样的信息处理方法和过程,以过程的抽象来对待系统的需要。而面向对象的分析直接映射问题空间,将问题空间中的实例抽象为对象,用对象的结构反映问题空间的复杂实例和复杂关系,用属性和操作表示实例的特性和行为。

OOA 的结果是为后面阶段类的选定和实现、类层次结构的组织和实现提供平台。因此,对OOA 的测试,应从以下方面考虑:

3. 面向对象设计的测试

传统的结构化设计方法,采用面向作业的思路,它把系统分解以后,提出一组作业,这些作业是以过程实现系统的基础构造,把问题域的分析转化为求解域的设计,分析的结果是设计阶段的输入。而 OOD 以 OOA 为基础归纳出类,并建立类结构或进一步构造成类库,实现分析结果对问题空间的抽象。由此可见,OOD 不是 OOA 的另一思维方式,而是 OOA的进一步细化和更高层的抽象,OOD 与 OOA 的界限通常是难以严格区分的。OOD 确定类和类结构不仅是满足当前需求分析的要求,更重要的是通过重新组合或加以适当的补充,能够方便地实现功能的重用和扩充,以不断适应用户的要求。因此,对 OOD 的测试,应从如下三方面考虑:

4. 面向对象编程的测试

典型的面向对象程序具有继承、封装和多态的新特性,这使得传统的测试策略必须有所改变。封装是对数据的隐藏,外界只能通过被提供的操作来访问或修改数据,这样降低了数据被任意修改和读写的可能性,降低了传统程序中对数据非法操作的测试。继承是面向对象程序的重要特点,继承使得代码的重用性提高,同时也使错误传播的概率提高。多态使得面向对象程序对外呈现出强大的处理能力,但同时却使得程序内 “同一”函数的行为复杂化,测试时不得不考虑对于不同类型参数具体执行的代码和产生的行为。

面向对象程序是把功能的实现分布在类中。能正确实现功能的类,通过消息传递来协同实现设计要求的功能。因此,在 OOP 阶段,忽略类功能实现的细则,将测试的焦点集中在类功能的实现和相应的面向对象程序风格,主要体现为以下两个方面。

5. 面向对象的单元测试

传统的单元测试的对象是软件设计的最小单位——模块,测试依据是详细设计说明书。

单元测试应对模块内所有重要的控制路径设计测试用例,以便发现模块内部的错误。单元测试多采用白盒测试技术,系统内多个模块可以并行地进行测试。

对于面向对象的软件,单元的概念发生了变化。每个类和类的实例(对象)封装了属性(数据)和操纵这些数据的操作,而不是个体的模块,最小的可测试单位变成了封装的类或对象。因此,单元测试的意义发生了较大变化。不再孤立地测试单个操作,而是将操作作为类的一部分。

6. 面向对象的集成测试

传统的集成测试,有两种典型的集成策略:

面向对象的软件没有层次控制结构,传统的自顶向下和自底向上集成策略已无太大意义。

此外,一次集成一个操作到类中(传统的增量集成方法)通常是不可能的。对 OO 软件的集成测试也有两种不同策略:第一种称为基于线程的测试,集成系统的一个输入或事件所需的一组类,每个线程被集成并分别测试,并使用回归测试以保证没有产生副作用。第二种称为基于使用的测试,首先测试那些几乎不使用其他类的类(称为独立类)并开始构造系统,在独立类测试完成后,下一层的使用独立类的类(称为依赖类)被测试。这个依赖类层次的测试序列一直持续到构造完整个系统。

7. 面向对象的系统测试

通过单元测试和集成测试,仅能保证软件开发的功能得以实现。但不能确认在实际运行时,它是否满足用户的需要。为此,对完成开发的软件必须经过规范的系统测试。系统测试应该尽量搭建与用户实际使用环境相同的测试平台,应该保证被测系统的完整性,对临时没有的系统设备部件,也应有相应的模拟手段。系统测试时,应该参考 OOA 分析的结果,对应描述的对象、属性和各种服务,检测软件是否能够完全“再现”问题空间。系统测试不仅是检测软件的整体行为表现,从另一个侧面看,也是对软件开发设计的再确认。

希赛教育专家提示:面向对象测试的整体目标是以最小的工作量发现最多的错误,与传统软件测试的目标是一致的,但 OO 测试的策略与传统测试有很大不同。这种不同主要体现在两个方面,第一,测试的焦点从过程构件(模块)移向了类;第二,测试的视角扩大到了分析和设计模型。