第2章软件测试过程与策略本章概述软件产品种类繁多,测试过程千变万化,为了能够找到系统中绝大部分的软件缺陷,必须构建各种行之有效的测试方法与策略。本章通过详细分析,介绍了软件测试的复杂性和经济性;通过讲述软件测试的整个流程,从而了解单元测试、集成测试、确认测试、系统测试和验收测试等基本测试方法;通过比较分析,介绍了静态与动态测试、黑盒与白盒测试的基本策略。第2章软件测试过程与策略2.1软件测试的复杂性与经济性分析2.2软件测试流程2.3静态测试与动态测试2.4黑盒测试与白盒测试小结习题2.1软件测试的复杂性与经济性分析人们对软件工程开发的常规认识中,认为开发程序是一个复杂而困难的过程,需要花费大量的人力、物力和时间,而测试一个程序则比较容易,不需要花费太多的精力。这其实是人们对软件工程开发过程理解上的一个误区。在实际的软件开发过程中,作为现代软件开发工业一个非常重要的组成部分,软件测试正扮演着越来越重要的角色。随着软件规模的不断扩大,如何在有限的条件下对被开发软件进行有效的测试正成为软件工程中一个非常关键的课题。2.1.1软件测试的复杂性设计测试用例是一项细致并且需要具备高度技巧的工作,稍有不慎就会顾此失彼,发生不应有的疏漏。下面分析了容易出现问题的根源。(1)完全测试是不现实的(2)软件测试是有风险的(3)杀虫剂现象(4)缺陷的不确定性图2-1的最优测试量示意图说明了发现软件缺陷数量和测试量之间的关系,随着测试量的增加,测试成本将呈几何数级上升,而软件缺陷降低到某一数值之后将没有明显的变化,最优测量值就是这两条曲线的交点。图2-1最优测试量示意图2.1.2软件测试的经济性软件测试的经济性有两方面体现:一是体现在测试工作在整个项目开发过程中的重要地位,二是体现在应该按照什么样的原则进行测试,以实现测试成本与测试效果的统一。测试是软件生存期中费用消耗最大的环节。测试费用除了测试的直接消耗外,还包括其它的相关费用。影响测试费用的主要因素有:(1)软件面向的目标用户(2)可能出现的用户数量(3)潜在缺陷造成的影响(4)开发机构的业务能力2.1.3软件测试的充分性准则软件测试的充分性准则有以下几点:对任何软件都存在有限的充分测试集合;当一个测试的数据集和对于一个被测的软件系统的测试是充分的,那么再多增加一些测试数据仍然是充分的。这一特性称为软件测试的单调性;即使对软件所有成分都进行了充分的测试,也并不意味着整个软件的测试已经充分了。这一特性称为软件测试的非复合性;即使对一个软件系统整体的测试是充分的,也并不意味着软件系统中各个成分都已经充分地得到了测试。这个特性称为软件测试的非分解性;软件测试的充分性与软件的需求、软件的实现都相关;软件测试的数据量正比于软件的复杂度。这一特性称为软件测试的复杂性;随着测试次数的增加,检查出软件缺陷的几率随之不断减少。软件测试具有回报递减率。2.1.4软件测试的误区在实际的项目开发与管理中仍然存在很多管理上或者技术上的误区。(1)期望用测试自动化代替大部分人工劳动(2)忽视需求阶段的参与(3)软件测试是技术要求不高的岗位图2-2V模型示意图2.2.1软件开发的V模型1.V模型软件开发流程的V模型是一个广为人知的模型,如图2-2所示。2.2软件测试流程2.软件测试过程软件测试过程按各测试阶段的先后顺序可分为单元测试、集成测试、确认(有效性)测试、系统测试和验收(用户)测试5个阶段,如图2-3所示。(1)单元测试:测试执行的开始阶段。测试对象是每个单元。测试目的是保证每个模块或组件能正常工作。单元测试主要采用白盒测试方法,检测程序的内部结构。(2)集成测试:也称组装测试。在单元测试基础上,对已测试过的模块进行组装,进行集成测试。测试目的是检验与接口有关的模块之间的问题。集成测试主要采用黑盒测试方法。(3)确认测试:也称有效性测试。在完成集成测试后,验证软件的功能和性能及其他特性是否符合用户要求。测试目的是保证系统能够按照用户预定的要求工作。确认测试通常采用黑盒测试方法。(4)系统测试:在完成确认测试后,为了检验它能否与实际环境(如软硬件平台、数据和人员等)协调工作,还需要进行系统测试。可以说,系统测试之后,软件产品基本满足开发要求。(5)验收测试:测试过程的最后一个阶段。验收测试主要突出用户的作用,同时软件开发人员也应该参与进去。2-3测试各阶段示意图软件测试阶段的输入信息包括两类:软件配置:指测试对象。通常包括需求说明书、设计说明书和被测试的源程序等;测试配置:通常包括测试计划、测试步骤、测试用例以及具体实施测试的测试程序、测试工具等。对测试结果与预期的结果进行比较以后,即可判断是否存在错误,决定是否进入排错阶段,进行调试任务。对修改以后的程序要进行重新测试,因为修改可能会带来新的问题。通常根据出错的情况得到出错率来预估被测软件的可靠性,这将对软件运行后的维护工作有重要价值。2.2.2单元测试1.单元测试的定义单元测试(UnitTesting)是对软件基本组成单元进行的测试。单元测试的对象是软件设计的最小单位——模块。一个菜单、一个显示界面或者能够独立完成的具体功能都可以是一个单元。某种意义上单元的概念已经扩展为组件(component)。单元测试通常是开发者编写的一小段代码,用于检验被测代码的一个很小的、很明确的功能是否正确。通常而言,一个单元测试是用于判断某个特定条件(或者场景)下某个特定函数的行为。2.单元测试的目标单元测试的主要目标是确保各单元模块被正确地编码。单元测试除了保证测试代码的功能性,还需要保证代码在结构上具有可靠性和健全性,并且能够在所有条件下正确响应。进行全面的单元测试,可以减少应用级别所需的工作量,并且彻底减少系统产生错误的可能性。如果手动执行,单元测试可能需要大量的工作,自动化测试会提高测试效率。3.单元测试的内容单元测试的主要内容有:模块接口测试;局部数据结构测试;独立路径测试;错误处理测试;边界条件测试。如图2-4所示,这些测试都作用于模块,共同完成单元测试任务。图2-4单元测试任务4.单元测试的步骤通常单元测试在编码阶段进行。当源程序代码编制完成,经过评审和验证,确认没有语法错误之后,就开始进行单元测试的测试用例设计。利用设计文档,设计可以验证程序功能、找出程序错误的多个测试用例。对于每一组输入,应有预期的正确结果。模块并不是一个独立的程序,在考虑测试模块时,同时要考虑它和外界的联系,用一些辅助模块去模拟与被测模块相关联的其它模块。这些辅助模块可分为两种:(1)驱动模块(driver):相当于被测模块的主程序。它接收测试数据,把这些数据传送给被测模块,最后输出实测结果。(2)桩模块(stub):用以代替被测模块调用的子模块。桩模块可以做少量的数据操作,不需要把子模块所有功能都带进来,但不允许什么事情也不做。被测模块、与它相关的驱动模块以及桩模块共同构成了一个“测试环境”,如图2-5所示。图2-5单元测试环境5.采用单元测试的原因程序员编写代码时,一定会反复调试保证其能够编译通过。如果是编译没有通过的代码,没有任何人会愿意交付给自己的老板。但代码通过编译,只是说明了它的语法正确,程序员却无法保证它的语义也一定正确。没有任何人可以轻易承诺这段代码的行为一定是正确的。单元测试这时会为此做出保证。编写单元测试就是用来验证这段代码的行为是否与软件开发人员期望的一致。有了单元测试,程序员可以自信的交付自己的代码,而没有任何的后顾之忧。图2-6各测试阶段发现缺陷的费用单元测试的成本效率大约是集成测试的两倍、系统测试的三倍,如图2-6所示。2.2.3集成测试1.集成测试的定义集成测试的定义是根据实际情况对程序模块采用适当的集成测试策略组装起来,对系统的接口以及集成后的功能进行正确校验的测试工作。集成测试是针对程序整体结构的测试。2.集成测试的层次软件的开发过程是一个从需求到概要设计、详细设计以及编码的逐步细化的过程,那么单元测试到集成测试再到系统测试就是一个逆向求证的过程。集成测试内部对于传统软件和面向对象的应用系统有两种层次的划分。对于传统软件来讲,可以把集成测试划分为三个层次:模块内集成测试;子系统内集成测试;子系统间集成测试。对于面向对象的应用系统来说,可以把集成测试分为两个阶段:类内集成测试;类间集成测试。3.集成测试的模式把模块组装成为系统的测试方式有两种:(1)一次性集成测试方式(No-IncrementalIntegration)一次性集成测试方式也称作非增值式集成测试。先分别测试每个模块,再把所有模块按设计要求放在一起结合成所需要实现的程序。图2-7是按照一次性集成测试方式的实例。图2-7(a)所示表示的是整个系统结构,共包含6个模块。具体测试过程如下:如图2-7(b)所示,为模块B配备驱动模块D1,来模拟模块A对B的调用。为模块B配备桩模块S1,来模拟模块C被B调用。对模块B进行单元测试;如图2-7(d)所示,为模块D配备驱动模块D3以及桩模块S2。对模块D进行单元测试;如图2-7(c)、图2-7(e)、图2-7(f)所示,为模块C、E、F分别配备驱动模块D2、D4、D5。对模块C、E、F分别进行单元测试;如图2-7(g)表示,为主模块A配备三个桩模块S3、S4、S5。对模块A进行单元测试;在将模块A、B、C、D、E分别进行了单元测试之后,再一次性进行集成测试;测试结束。图2-7一次性集成测试方式(2)增值式集成测试方式把下一个要测试的模块同已经测好的模块结合起来进行测试,测试完毕,再把下一个应该测试的模块结合进来继续进行测试。在组装的过程中边连接边测试,以发现连接过程中产生的问题。通过增值逐步组装成为预先要求的软件系统。增值式集成测试方式有三种:自顶向下增值测试方式(Top-downIntegration)主控模块作为测试驱动,所有与主控模块直接相连的模块作为桩模块;根据集成的方式(深度或广度),每次用一个模块把从属的桩模块替换成真正的模块;在每个模块被集成时,都必须已经进行了单元测试;进行回归测试以确定集成新模块后没有引入错误。这种组装方式将模块按系统程序结构,沿着控制层次自顶向下进行组装。自顶向下的增值方式在测试过程中较早地验证了主要的控制和判断点。选用按深度方向组装的方式,可以首先实现和验证一个完整的软件功能。图2-8表示的是按照深度优先方式遍历的自顶向下增值的集成测试实例。具体测试过程如下:在树状结构图中,按照先左后右的顺序确定模块集成路线;如图2-8(a)所示,先对顶层的主模块A进行单元测试。就是对模块A配以桩模块S1、S2和S3,用来模拟它所实际调用的模块B、C、D,然后进行测试;如图2-8(b)所示,用实际模块B替换掉桩模块S1,与模块A连接,再对模块B配以桩模块S4,用来模拟模块B对E的调用,然后进行测试;图2-8(c)是将模块E替换掉桩模块S4并与模块B相连,然后进行测试;判断模块E没有叶子节点,也就是说以A为根节点的树状结构图中的最左侧分支深度遍历结束。转向下一个分支;图2-8(d)所示,模块C替换掉桩模块S2,连到模块A上,然后进行测试;判断模块C没有桩模块,转到树状结构图的最后一个分支;如图2-8(e)所示,模块D替换掉桩模块S3,连到模块A上,同时给模块D配以桩模块S5,来模拟其对模块F的调用。然后进行测试;如图2-8(f)所示,去掉桩模块S5,替换成实际模块F连接到模块D,然后进行测试;对树状结构图进行了完全测试,测试结束。图2-8自顶向下增值测试方式自底向上增值测试方式(Bottom-upIntegration)组装从最底层的模块开始,组合成一个构件,用以完成指定的软件子功能。编制驱动程序,协调测试用例的输入与输出;测试集成后的构件;按程序结构向上组装测试后的构件,同时除掉驱动程序。这种组装的方式是从程序模块结构的最底层的模块开始组装和测试。因为模块是自底向上进行组装,对于一个给定层次的模块,它的子模块(包括子模块的所有下属模块)已经组装并测试完成,所以不再需要