《软件测试的艺术》第五章 模块(单元)测试

参考资料

5.0 前言

大型的软件程序需要特别的测试对策。在本章中我们会探讨构建大型程序测试的第一个步骤:模块测试(单元测试),而剩余的步骤将在第六章和第七章中介绍。

模块测试是对程序中的单个子程序、子程序或过程进行测试的过程, 也就是说,一开始并不是对整个程序进行测试,而是先将注意力集中在对构成程序的较小的模块的测试上面。

选择模块测试的原因:

  1. 由于模块测试的注意力集中在程序的较小单元上,因此它是一种管理组合的测试元素的手段。
  2. 模块测试减轻了调试的难度,这是因为一旦某个错误被发现出来,我们就知道它在哪个具体的模块中。
  3. 模块测试为同时测试多个模块提供了可能,这将并行工程引入软件测试中。

5.1 测试用例设计

在为模块测试设计的测试用例时,需要使用两种类型的信息:模块的规格说明和模块的源代码。规格说明一般都规定了模块的输入和输出参数以及模块的功能。

模块测试的测试用例的设计过程如下:

使用一种或多种白盒测试方法分析模块的逻辑结构,然后使用黑盒测试方法对照模块的规格说明以补充测试用例。

白盒测试和黑盒测试测试用例的设计见第四章

(此处建议静下心来看书上所给的例子)

5.2 增量测试

这里需要考虑的问题是:软件测试是否应先独立地测试每个模块,然后再将这些模块组装成完整的程序?还是先将下一步要测试的模块组装到测试完成的模块集合中,然后再进行测试?第一种方法称为非增量测试或“崩溃”测试,而第二种方法称为增量测试或集成。
在这里插入图片描述
作为传统方法的非增量测试是按如下方式进行的:首先,对6个模块中的每一个模块进行单独的模块测试,将每个模块视为一个独立实体。根据环境和参与人数,这些模块可以同时或按次序进行测试。最后,将这些模块组装或集成为完整的程序。

测试单独的模块需要一个特殊的驱动模块和一个或多个桩模块。驱动模块是人们编写的一个小模块,用来将测试用例驱动或传输到被测模块中。驱动模块还必须向测试人员显示模块B的结果。举例来说,测试模块B,首先要设计测试用例,然后将测试用例作为输入参数由驱动模块传递给模块B。由于模块B调用了模块E,所以还必须使用一个额外的组件,该组件在模块B调用模块E时接受模块B的控制指令。这就由桩模块来完成,它是一个被命名为“E”的特殊模块,用来模拟模块E的功能。当所有6个模块的模块测试都完成之后,就将这些模块组装成完整的程序。

另一种可选择的方法是增量测试。增量测试首先将下一个要测试的模块组装到前面已经测试过的模块集合中去。

下面是几个显而易见的结论:

  1. 非增量测试所需的工作量要多一些,增量测试所需的工作量要少一些,因为使用了前面测试过的模块来取代非增量测试中所需要的驱动模块(如果从顶部开始)或桩模块(如果从底部开始测试)。
  2. 如果是使用了增量测试,可以较早地发现模块中与不匹配接口、不正确假设相关的编程错误。这是由于尽早地对模块组合进行了集成测试。
  3. 如果使用了增量测试,调试会进行得容易一些。假设存在着与模块间接口或假设相关的编程错误,如果使用增量测试,这种类型的错误就很容易发现,因为该错误很可能与最近添加的模块有关。
  4. 增量测试会将测试进行得更彻底。因为增量测试使用先前测试过的模块,取代了非增量测试中使用的驱动模块或桩模块,因此,到最后一个模块测试完成时,实际的模块经受到了更多的检验。
  5. 非增量测试所占用的机器时间显得少一些。
  6. 模块测试阶段开始时,如果使用的是非增量测试,就会有更多的机会进行并行操作。对于大型的软件项目,这可能非常重要,因为在模块开始测试之时,项目的人员数量往往处于最高峰。

由上我们可以得出结论:增量测试要更好一些。

5.3 自顶向下测试和自底向上测试

5.3.1 自顶向下的测试

自顶向下的测试是从程序的顶部或初始模块开始。测试开始之后,挑选哪一个后续模块进行增量测试没有惟一正确的方法;唯一的原则是:要成为合乎条件的下一个模块,至少一个该模块的从属模块(调用它的模块)事先经过了测试。
在这里插入图片描述
第一步是测试模块A,测试要求必须编写出代表B、C和D的桩模块。
桩模块和驱动模块的概念见此。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
另一个需要考虑的地方是采取什么样的形式将测试用例提交给程序。由于在典型的程序中,顶部模块既不接收输入参数,也不执行输入/输出操作,因此问题的答案并不显而易见:测试数据通过其一个或多个桩模块提交给模块 (此处为模块A)。

在本程序中还有另一个问题:由于假定模块A仅调用模块B一次,问题是如何将多个测试用例提交给模块A。一个解决办法是编写出桩模块B的多个版本,每一个版本都将一个各不相同的有效测试数据集返回给模块A。 为了执行这些测试用例,程序需要执行多次,每次都使用桩模块B的不同版本。另一种可选择的方法是将测试数据放置在外部文件中,由桩模块B读取并返回给模块A。

模块A测试完之后,就用一个实际的模块代替其中的一个桩模块,而该模块需要的桩模块也被添加进来。

在设计模块序列时应该考虑的因素:

  1. 如果程序中存在关键部分,那么在设计模块序列时就应将这些关键模块尽可能早地添加进去。 所谓“关键部分”可能是某个复杂的模块、某个采用新算法的模块或某个被怀疑容易发生错误的模块。
  2. 在设计模块序列时,应将I/O模块尽可能早地添加进来。

5.3.2 自底向上的测试

自底向上的策略开始于程序中的终端模块。测试完这些模块之后,同样没有最佳的方法来挑选要进行增量测试的下一个模块;惟一正确的原则是:要成为合乎条件的下一个模块,该模块所有的从属模块(它调用的模块)都已经事先经过了测试。
在这里插入图片描述
第一步是测试模块E、J、G、K、L和I中的部分或全部模块,既可以串行进行,也可以并行进行。要做到这一点,每一个模块都需要一个特殊的驱动模块 :即包含着有效的测试输入、调用被测模块且将输出显示出来(或将实际输出与预测输出作比较)的模块。有别于桩模块,由于驱动模块可以交迭地调用被测模块,因此不需要为驱动模块提供多个版本。 在大多数情况下,开发驱动模块要比开发桩模块更容易些。

自顶向下方法中无法建立所有测试环境的问题,在这里都不复存在。如果将驱动模块看作是一个测试探针的话,那么该探针是直接放入被测试模块中去的,不会受到中间模块的困扰。

5.3.3 比较

在这里插入图片描述

5.4 执行测试

  • 当测试用例造成模块输出的实际结果与预期结果不匹配的情况时,存在两个可能的解释:要么该模块存在错误,要么预期的结果不正确(测试用例不正确)。为了将这种混乱降到最小程度,应在测试执行之前对测试用例集进行审查或检查(也就是说,应对测试用例进行测试)。
  • 使用自动化测试工具可以使测试过程中的枯燥劳动减至最小。
  • 在准备模块测试时,重温一下心理学和经济学原则会有所裨益。如同本章前面所做的那样,记住对预期输出进行定义是测试用例必不可少的部分。在执行测试时,应该查找程序的副作用。一般情况下,这些情况都是很难发现的,但如果在测试用例执行完之后,检查那些不应有变动的模块输入,可能会发现一些错误实例。
  • 因个人试图测试自己编写的程序所带来的心理学问题,也适用于模块测试。编写调用模块的程序员始终是测试被调用模块的最佳候选人。注意,这仅仅适用于测试。
  • 应避免随意丢弃测试用例,应将它们按某种格式记录下来,以便将来可以重新使用它们。如果发现某一部分模块存在大量错误,那么很有可能这些模块甚至包含着更多的错误,只是尚未检查出来而已。这样的模块应该进行更进一步的测试,可能还需要进行额外的代码走查或检查。
  • 最后,记住模块测试的目的不是证明模块能够正确地运行,而是证明模块中存在着错误。

5.5 小结

本章讨论的单元测试技术对大型程序尤其有用。通过这种技术来测试程序的组件如子程序、子函数、类以及过程。单元测试用来检测软件的功能是否满足了规格说明书的要求。单元测试是开发者编写可靠程序的重要技术,尤其是那些使用面向对象语言的开发者。除了需要阅读程序规格说明书,单元测试还需要了解模块(单元)的源代码。

单元测试是大规模的白盒测试。彻底的单元测试设计需要使用增量策略,如自顶向下以及自底向上的技术。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值