机测试的方法发现错误之后,通常需要先改正这个错误才能继续测试,因此错误是一个一个地发现并改正的。也就是说,采用代码审查的方法可以减少系统验证的总工作量。
实践表明,对于查找某些类型的错误来说,人工测试比计算机测试更有效;对于其他类型的错误来说则刚好相反。因此,人工测试和计算机测试是互相补充,相辅相成的,缺少其中任何一种方法都会使查找错误的效率降低。 7.3.3 计算机测试
模块并不是一个独立的程序,因此必须为每个单元测试开发驱动软件和(或)存根软件。通常驱动程序也就是一个“主程序”,它接收测试数据,把这些数据传送给被测试的模块,并且印出有关的结果。存根程序代替被测试的模块所调用的模块。因此存根程序也可以称为“虚拟子程序”。它使用被它代替的模块的接口,可能做最少量的数据操作,印出对入口的检验或操作结果,并且把控制归还给调用它的模块。
例如,图7.2是一个正文加工系统的部分层次图,假定要测试其中编号为3.O的关键模块——正文编辑模块。因为正文编辑模块不是一个独立的程序,所以需要有一个测试驱动程序来调用它。这个驱动程序说明必要的变量,接收测试数据——字符串,并且设置正文编辑模块的编辑功能。因为在原来的软件结构中,正文编辑模块通过调用它的下层 正文加工系统 输出 编辑 存储 检索 编目录 格式化 加标题 输入 2.0 3.0 5.0 6.0 7.0 8.0 4.0 1.0 修改 合并 列表 添加 删除 插入 3.4 3.5 3.6 3.1 3.2 3.3 图7.2 正文加工系统的层次图
模块来完成具体的编辑功能,所以需要有存根程序简化地模拟这些下层模块。为了简单起见,测试时可以设置的编辑功能只有修改(CHANGE)和添加(APPEND)两种,用控制变量CFUNCT标记要求的编辑功能,而且只用一个存根程序模拟正文编辑模块的所有
下层模块。下面是用伪码书写的存根程序和驱动程序。 I.TEST STUB(*测试正文编辑模块用的存根程序*) 初始化;
输出信息“进入了正文编辑程序”; 输出“输入的控制信息是”CFUNCT; 输出缓冲区中的字符串; IF CFUNCT=CHANGE THEN
把缓冲区中第二个字改为 * * * ELSE
在缓冲区的尾部加??? END IF;
输出缓冲区中的新字符串; END TEST STUB
Ⅱ.TEST DRIVER(*测试正文编辑模块用的驱动程序*) 说明长度为2 500个字符的一个缓冲区; 把CFUNCT置为希望测试的状态; 输入字符串;
调用正文编辑模块; 停止或再次初启; END TEST DRIVER
驱动程序和存根程序代表开销,也就是说,为了进行单元测试必须编写测试软件,但是通常并不把它们作为软件产品的一部分交给用户。许多模块不能用简单的测试软件充分测试,为了减少开销可以使用下节将要介绍的渐增式测试方法,在集成测试的过程中同时完成对模块的详尽测试。
模块的内聚程度高可以简化单元测试过程。如果每个模块只完成一种功能,则需要的测试方案数目将明显减少,模块中的错误也更容易预测和发现。
7.4集成测试
集成测试是测试和组装软件的系统化技术,例如,子系统测试即是在把模块按照设计要求组装起来的同时进行测试,主要目标是发现与接口有关的问题(系统测试与此类似)。例如,数据穿过接口时可能丢失;一个模块对另一个模块可能由于疏忽而造成有害影响;把子功能组合起来可能不产生预期的主功能;个别看来是可以接受的误差可能积累到不能接受的程度;全程数据结构可能有问题等等。不幸的是,可能发生的接口问题多得不胜枚举。
由模块组装成程序时有两种方法。一种方法是先分别测试每个模块,再把所有模块按设计要求放在一起结合成所要的程序,这种方法称为非渐增式测试方法;另一种方法是把下一个要测试的模块同已经测试好的那些模块结合起来进行
测试,测试完以后再把下一个应该测试的模块结合进来测试。这种每次增加一个模块的方法称为渐增式测试,这种方法实际上同时完成单元测试和集成测试。这两种方法哪种更好一些呢?下面对比它们的主要优缺点: 非渐增式测试一下子把所有模块放在一起,并把庞大的程序作为一个整体来测试,测试者面对的情况十分复杂。测试时会遇到许许多多的错误,改正错误更是极端困难,因为在庞大的程序中想要诊断定位一个错误是非常困难的。而且一旦改正一个错误之后,马上又会遇到新的错误,这个过程将继续下去,看起来好像永远也没有尽头。
渐增式测试与“一步到位”的非渐增式测试相反,它把程序划分成小段来构造和测试,在这个过程中比较容易定位和改正错误;对接口可以进行更彻底的测试;可以使用系统化的测试方法。因此,目前在进行集成测试时普遍采用渐增式测试方法。
当使用渐增方式把模块结合到程序中去时,有自顶向下和自底向上两种集成策略。
7.4.1 自顶向下集成
自顶向下集成方法是一个日益为人们广泛采用的测试和组装软件的途径。从主控制模块开始,沿着程序的控制层次向下移动,逐渐把各个模块结合起来。在把附属于(及最终附属于)主控制模块的那些模块组装到程序结构中去时,或者使用深度优先的策略,或者使用宽度优先的策略。
参看图7.3,深度优先的结合方法先组装在软件结构的一条主控制通路上的所有模块。选择一条主控制通路取决于应用的特点,并且有很大任意性。例如,选取左通路,首先结合模块M1、M2和M5;其次,M8或M6(如果为了使M。具有适当功能需要M6)将被结合进来。然后构造中央的和右侧的控制通路。而宽度优先的结合方法是沿软件结构水平地移动,把处于同一个控制层次上的所有模块组装起来。对于图7.3来说,首先结合模块M2、M3和M4(代替存根程序S4),然后结合下一个控制层次中的模块M5、M6和M7;如此继续进行下去,直到所有模块都被结合进来为止。
M1
M3 S4 M2
M6 S7 M5
M8
图7.3 自顶向下结合
把模块结合进软件结构的具体过程由下述4个步骤完成: 第一步,对主控制模块进行测试,测试时用存根程序代替所有直接附属于主控制模块的模块; 第二步,根据选定的结合策略(深度优先或宽度优先),每次用一个实际模块代换一个存根程序(新结合进来的模块往往又需要新的存根程序); 第三步,在结合进一个模块的同时进行测试; 第四步,为了保证加入模块没有引进新的错误,可能需要进行回归测试(即,全部或部分地重复以前做过的测试)。
从第二步开始不断地重复进行上述过程,直到构造起完整的软件结构为止。图7.3描绘了这个过程。假设选取深度优先的结合策略,软件结构已经部分地构造起来了,下一步存根程序S7,将被模块M7,取代。M7可能本身又需要存根程序,以后这些存根程序也将被相应的模块所取代。
自顶向下的结合策略能够在测试的早期对主要的控制或关键的抉择进行检验。在一个分解得好的软件结构中,关键的抉择位于层次系统的较上层,因此首先碰到。如果主要控制确实有问题,早期认识到这类问题是很有好处的,可以及早想办法解决。如果选择深度优先的结合方法,可以在早期实现软件的一个完整的功能并且验证这个功能。早期证实软件的一个完整的功能,可以增强开发人员和用户双方的信心。
自顶向下的方法讲起来比较简单,但是实际使用时可能遇到逻辑上的问题。这类问题中最常见的是,为了充分地测试软件系统的较高层次,需要在较低层次上的处理。然而在自顶向下测试的初期,存根程序代替了低层次的模块,因此,在软件结构中没有重要的数据自下往上流。为了解决这个问题,测试人员有两种选择:第一,把许多测试推迟到用真实模块代替了存根程序以后再进行;第二,从层次系统的底部向上组装软件。第一种方法失去了在特定的测试和组装特定的模块之间的精确对应关系,这可能导致在确定错误的位置和原因时发生困难。后一种方法称为自底向上的测试,下面讨论这种方法。 7.4.2 自底向上集成
自底向上测试从“原子”模块(即在软件结构最低层的模块)开始组装和测试。因为是从底部向上结合模块,总能得到所需的下层模块处理功能,所以不需要存根程序。
用下述步骤可以实现自底向上的结合策略:
第一步,把低层模块组合成实现某个特定的软件子功能的族; 第二步,写一个驱动程序(用于测试的控制程序),协调测试数据的输入和输出;
第三步,对由模块组成的子功能族进行测试;
第四步,去掉驱动程序,沿软件结构自下向上移动,把子功能族组合起来形成更大的子功能族。
上述第二步到第四步实质上构成了一个循环。图7.4描绘了自底向上的结合过程。首先把模块组合成族1、族2和族3,使用驱动程序(图中用虚线方框表示)对每个子功能族进行测试。族1和族2中的模块附属于模块Ma,去掉驱动程序
族1 D1和D2,把这两个族直接同Ma连接起来。类似地,在和模块Mb结合之前去掉族3的驱动程序D3。最终M8和Mb这两个模块都与模块Mc结合起来。 Mc Mb Ma Mc D3 D2 D1 族3 族2
图7.4 自底向上结
合合
随着结合向上移动,对测试驱动程序的需要也减少了。事实上,如果软件结构的顶部两层用自顶向下的方法组装,可以明显减少驱动程序的数目,而且族的结合也将大大简化。 7.4.3 不同集成测试策略的比较
上面介绍了集成测试的两种策略,到底哪种方法更好一些呢?一般说来,一种方法的优点正好对应于另一种方法的缺点。自顶向下测试方法的主要优点是不需要测试驱动程序,能够在测试阶段的早期实现并验证系统的主要功能,而且能在早期发现上层模块的接口错误。自顶向下测试方法的主要缺点是需要存根程序,可能遇到与此相联系的测试困难,低层关键模块中的错误发现较晚,而且用这种方法在早期不能充分展开人力。可以看出,自底向上测试方法的优缺点与上述自顶向下测试方法的优缺点刚好相反。 在测试实际的软件系统时,应该根据软件的特点以及工程进度安排,选用适当的测试策略。一般说来,纯粹自顶向下或纯粹自底向上的策略可能都不实用,人们在实践中创造出许多混合策略: