16.2 集成测试方法
集成测试有多种策略,例如自顶向下、自底向上、三明治集成、基干集成、面向对象的集成等。我们选择其中一些使用较为普遍的策略来进行介绍。若以模块调用图作为集成测试的基础,则针对单个测试用例的设计可以分为两种测试方法:成对集成和邻居集成。结合测试用例的设计与调用图的遍历,可以分为四种测试方法:大爆炸方法、自顶向下、自底向上和三明治集成方法。
16.2.1 成对集成
1. 基本思想
成对集成的基本思想是将每个集成测试用例限定在一对调用单元上,即每个集成测试用例都是最小的集成单元,仅涉及一对调用的接口。这样做最大的好处就是使得缺陷非常容易定位,一旦某个集成测试用例失败,可以肯定地说是该用例涉及的这一对模块的接口有问题。
2. 案例实践:NextDate问题的成对集成
以NextDate问题为例,图16-3是NextDate问题的模块调用图,两个典型的成对集成用例如图16-4所示〔 见图中虚线框框住的灰色区域)。

图16-3 集成版本的NextDate 图16-4 成对集成的测试用例设计
问题的模块调用图:
成对集成的测试用例集合规模是可以精确计算的。设某模块调用图中包含m 个模块,共有n 条边,则因每条边对应一对调用接口,确定一个成对集成用例,因此,该调用图的集成测试应包含n 个测试用例。我们发现,成对集成的测试用例规模与模块的数量没有直接关系。但一般而言,调用图中包含的模块数目越多,则成对集成的测试用例数量也越大。
16.2.2 邻居集成
1. 基本思想
邻居集成的基本思想是将每个集成测试用例限定在某个节点的邻居上,即针对某个模块的集成测试用例应同时包含该模块及其邻居。所谓邻居,是一个特定的模块集合,它包括某个模块、所有调用该模块的上层模块以及所有被该模块调用的下层模块。邻居的构成有两种方式:
1)处于中间层的模块
对于每个处于调用图中间层的模块而言,它既有上层调用模块,又有下层被调用模块,可以自然形成一组邻居,构成一个集成测试用例。
2)根节点直接调用叶子节点
当有根节点模块直接调用叶子节点模块时,根模块与所有被它调用的叶子模块共同形成一组邻居,构成一个集成测试用例。
2. 案例实践:NextDate 问题的邻居集成
以NextDate 问题为例,两个典型的邻居集成用例如图16-5所示(见图中虚线框框住的灰色区域)。

图16-5 邻居集成的测试用例设计
与成对集成相比,邻居集成试图通过扩大单个测试用例所覆盖的模块接口的范围来减少测试用例总数,但导致的结果是致使缺陷的定位变得困难。邻居集成可以降低桩模块和驱动模块的开发工作量。
另一方面,从邻居集成自身而言,某个模块可能包含在多个测试用例中,一旦修改了该模块,这意味着所有包含该模块的测试用例需要重新测试一遍,这样将大大增加集成测试的工作量。
16.2.3 大爆炸集成
1. 基本思想
大爆炸(Big Bans)集成是将所有经过单元测试的模块一次性组装到被测系统中进行测试,完全不考虑模块之间的依赖性和可能的风险。
2.案例实践:NextDate 问题的大爆炸集成
以NextDate 问题为例,大爆炸集成的用例如图16-6所示(见图中虚线框框住的灰色区域)。

图16-6 大爆炸集成测试用例设计
我们看到,大爆炸集成仅需一个测试用例,达到用例规模的最小化。同时,由于将所有模块包含进来,不涉及桩模块和驱动模块的开发工作,然而,该测试用例包含所有模块的接口,直接导致缺陷定位的困难,一旦用例失败,完全不知道是哪对模块的调用接口出了问题。特别地,即使被测系统能够一次性集成成功,也会有许多接口的缺陷逃过测试而进入系统测试,给系统测试带来不良影响,大大增加系统测试的负担。
16.2.4 自顶向下的集成
1. 基本思想
自顶向下(Top Down)的集成是从主控模块(主程序,即根节点)开始,按照系统程序结构,沿着控制层次从上而下,逐渐将各模块组装起来,在从上向下的集成测试过程中,需对那些未经集成测试的模块开发桩模块。在集成过程中,可以采用宽度优先或深度优先的策略向下推进。具体步骤如下。
(l)对根节点进行集成测试,所有被根节点直接调用的模块均用桩模块来代替。
(2)根据选择的推进策略(宽度优先或深度优先),用实际模块替换桩模块(一般每次仅替换一个),并用新的桩模块代替新加入的模块,与已测模块或子系统构成新的子系统,进行测试。
(3)回归测试,即全部或部分执行以前做过的测试,以确保新加入的模块未引入新的缺陷。
(4)重复步骤(2)(3),直至所有模块都已集成到系统中。
2. 案例实践:NextDate 问题的自顶向下集成
以NextDate 问题为例,以宽度优先策略自顶向下进行集成测试,从根节点开始,依次测试的节点为:
NextDate3àGetDateàValidDateàIncrcmentDateàPrintDateàlastDayofMonthàisLeapYear
对应集成用例如图16-7所示。图中带斜线阴影的小圆圈表示被调用模块所对应的桩。
以深度优先策略自顶向下进行集成测试,从根节点开始,依次测试的节点为:NextDate3àGetDateàValidoateàlastDayofMonthàisLeapYear àIncrementoateàPrintData,对应集成测试用例如图16-8所示。其中测试NextDate3、加入GetDate、加入ValidDate的图与图16-7 的图(a)(b)(c)完全相同,不再画出。出现这种情况的原因是ValidDate 既被NextDate3调用又被GetDate 调用,因此,它既是NextDate3的下层节点,又是GctDate的下层节点。
自顶向下集成策略的优势如下:
Ø 有助于早期实现并验证系统主要功能,同时给开发团队和用户带来成功的信心。
Ø 利于早期验证主要的控制和判断,避免主要控制方面的缺陷,确保开发进度。
Ø 可以早期发现上层模块的接口错误。
有利就有弊,我们不妨从以下方面来分析自顶向下策略的不足:
Ø 相比大爆炸集成,自顶向下的集成需要大量的集成测试用例。
Ø 需要开发桩模块,大量桩模块的开发和维护成为自顶向下集成中最主要的成本。

图16-7 以宽度优先策略自顶向下集成的测试用例设计

图16-8 以深度优先策略自顶向下集成的测试用例设计
Ø 复杂的算法往往存在于底层模块中,自顶向下的集成往往到了测试的最后阶段才能发现算法中的问题。
Ø 不利于测试的并行,难以充分展开人力。
在用实际模块替换桩模块的过程中,我们一般推荐一次仅替换一个桩模块,这样容易定位缺陷,不过,这样的做法也容易引发一个测试爆炸的问题,即每次替换一个桩模块之后要进行大范围的回归测试,由此导致测试工作量的激增也是非常可怕的。
16.2.5 自底向上的集成
1. 基本思想
自底向上(Bottom Up)的集成是从最底层模块(即叶子节点)开始,按照调用图的结构,从下而上,逐层将各模块组装起来。在从下向上的集成测试过程中,需对那些未经集成测试的模块开发驱动模块。具体步骤如下。
(l)对叶子节点进行集成测试,所有直接调用叶子节点的模块均用驱动模块来代替。
(2)用实际模块替换驱动模块(一般每次仅替换一个),并用新的驱动模块代替新加入的模块,与下层所有已测的被调用模块构成新的子系统(子功能),进行测试。
(3)回归测试,即全部或部分执行以前做过的测试,以确保新加入的模块未引入新的缺陷。
(4)重复步骤(2)(3),直至所有模块都已集成到系统中。
2.案例实践:NextDate问题的自底向上集成
以NextDate 问题为例,以宽度优先策略自底向上进行集成测试的用例如图16-9所示。图中带斜线阴影的小圆圈表示上层调用模块所对应的驱动,虚线框表示同一个调用模块.这里较为特殊的是存在某模块同时被多个上层模块所调用的情况,此时可利用路径的概念将多个调用模块所形成的路径拆开,分别进行分析。
自底向上的集成需要开发驱动模块,且驱动模块的数量也是可以估算出来的。假设调用图中有m 个模块,其中叶子模块为n 个,则集成测试所需开发的驱动模块的数量为m-n 个。值得注意的是:驱动模块的数量相比自顶向下集成中桩模块的数量要少很多,而且驱动模块的开发难度较桩模块的开发难度要小一些。
16.2.6 三明治集成
1.基本思想
三明治(Sandwich)集成是将自顶向下和自底向上集成方法结合起来的集成策略。在调用图上按照一定的策略,分别从顶向下和自底向上展开集成,并在子树上进行大爆炸集成。策略之一是将系统划分为三层,中间层为目标层,测试时对目标层上面的层使用自顶向下的集成策略,对目标层下面的层使用自底向上的集成策略。采用这种策略可以降低桩模块和驱动模块的开发工作量,并同时拥有自顶向下及自底向上集成的优点,然而,由于最终在目标层进行大爆炸集成,所以目标层的模块无法得到充分的测试。
策略之二是在第一种集成策略的基础上,对目标层采用独立测试的策略,以确保目标层模块在集成测试之前得到充分的测试。不过,由此引入更多的测试用例,易导致桩模块和驱动模块开发工作量的迅速增加。



图16-9 自底向上集成的测试用例设计
策略之三是对包含读操作的子系统自底向上集成测试直至根节点,然后对包含写操作的子系统自顶向下集成测试直至叶子节点。
2.案例实践:NextDate 问题的三明治集成
以NextDate 问题为例,采用第一种策略进行三明治集成,本例中有部分函数同时处于调用图的不同层次,如函数valiDate 既在第三层(从上往下),又在第二层,可结合函数的具体调用情况决定究竟在哪里进行最终的大爆炸集成。本例中选择从NextDate 开始自顶向下集成,直至加入GetDate ,同时从IsLeapYear 和PrintData 开始自底向上集成,直至加入lastDayofMonth。集成的过程如图16-30所示。从该过程可以明显看出,目标层的函数validDate和Incrementoate没有得到充分的测试。



图16-30 第一种三明治集成策略
仍以NextDate 问题为例,采用第二种策略进行三明治集成,测试过程如图16-31所示。其中图(a)(b)和最后一个环节与第一种策略完全一样,不再画出,仅画出图16-30(b)(c) 之间增加的集成测试部分。它其实就是自底向上集成中的第三个步骤(见图16-29(c))。
以NextDate 问题为例,采用第三种策略进行三明治集成。其中函数GetDate 执行数据的读入和有效性校验,包含读操作,应自底向上集成测试,函数PrintData执行数据的输出,包含写操作,应自顶向下集成测试。整个测试过程如图16-32所示,图(a)-(d)是自底向上集成测试的过程,图(e)-(f)是自顶向下集成测试的过程,最终进行大爆炸集成。

图16-31 第二种三明治集成策略

图16-32 第三种三明治集成策略
3.特点分析
三明治集成将自顶向下和自底向上的集成有机结合起来,能够充分体现二者的优势,表现在如下几个方面:
Ø 易于早期发现主要控制部分的缺陷。
Ø 易于早期观察到系统的主要运行概貌。
Ø 易于早期发现底层复杂算法的缺陷。
Ø 易于并行测试,便于展开人力。
三明治集成也有其弊端,体现在如下方面:
Ø 中间的目标层可能得不到充分的测试。
Ø 需要同时开发桩模块和驱动模块,这部分工作量可能是相当惊人的。
Ø 需在子树上进行大爆炸集成,一旦发现缺陷,涉及的接口数量较多,导致缺陷定位难度增加。
16.2.7 几种集成策略的比较
表16-1 对以上分析的集中集成策略进行了简要的比较。表中N/A表示无需考虑的问题
表16-1 集中集成策略的比较

小结
集成测试是单元测试之后、系统测试之前的一个重要环节,从某种意义上来说,集成测试是三个阶段中最关键的一步。集成测试的策略主要围绕单个集成测试用例对接口的覆盖和对整个集成树的遍历路径进行设计,各种策略在测试用例的规模、驱动和桩模块的工作量以及缺陷定位等方面各有千秋,应根据实际情况灵活使用。

