资源描述
软件测试:模块(单元)测试
到目前为止,我们在很大程度上忽视了软件测试的机制及被测程序的规模。然 而,大型的软件程序(即超过 500 条语句块的程序)需要特别的测试对策。在本章 中我们将探讨构建大型程序测试的第一个步骤:模块测试,而剩余的步骤将在本书 第 6 章中介绍。
模块测试(或单元测试)是对程序中的单个子程序、子程序或过程进行测试的 过程,也就是说,一开始并不是对整个程序进行测试,而是首先将注意力集中在对 构成程序的较小模块的测试上面。这样做的动机有三个。首先,由于模块测试的注 意力一开始集中在程序的较小单元上,因此它是一种管理组合的测试元素的手段。 其次,模块测试减轻了调试(准确定位并纠正某个已知错误的过程)的难度,这是 因为一旦某个错误被发现出来,我们就知道它在哪个具体的模块中。第三,模块测 试通过为我们提供同时测试多个模块的可能,将并行工程引入软件测试中。
模块测试的目的是将模块的功能与定义模块的功能规格说明或接口规格说明 进行比较。为了再次强调所有测试过程的目的,这里的测试目标不是为了说明模块 符合其规格说明,而是为了揭示出模块与其规格说明存在着矛盾。在本章中,我们 从以下三个方面来探讨模块测试:
1. 测试用例的设计方式。
2. 模块测试及集成的顺序。
3. 对执行模块测试的建议。
5.1 测试用例设计
在为模块测试设计测试用例时,需要使用两种类型的信息:模块的规格说明和 模块的源代码。规格说明一般都规定了模块的输入和输出参数以及模块的功能。
模块测试总体上是面向白盒测试的。其中一个原因是如果对大一点的软件进行 测试,例如一个完整的程序(其实是后续的测试过程所针对的对象),白盒测试不 容易展开。第二个原因是,后续的测试过程着眼于发现其他类型的错误(举例来说,
这些错误不一定与程序的逻辑结构有关,比如程序未能满足其用户需求)。因此,
模块测试的测试用例的设计过程如下:使用一种或多种白盒测试方法分析模块的逻 辑结构,然后使用黑盒测试方法对照模块的规格说明以补充测试用例。
由于所需的测试用例设计方法已经在本书第 4 章中讨论过,我们在这里通过一 个例子来描述这些方法在模块测试中的应用。假设我们要测试一个名为BONUS的 模块。其功能是为销售额最高的部门的雇员的薪水增加$2,000,但是如果某个符合 条件的雇员的当前工资己经达到或超过了$150,000,则薪水只增加$1000 。2
对模块的输入情况如图 5-1 中的表格所示。如果模块正确完成了其功能,返回 错误代码 0。如果雇员或部门表中不存在任何条目,模块将返回错误代码 l。如果在 某个符合条件的部门中未发现任何雇员,模块将返回错误代码 2。
图 5-1 BONUS 模块的输入表
模块的源程序如图 5-2 所示。输入参数 ESIZE 和 DSIZE 分别代表雇员表和部 门表内条目的数量。该模块是用 PL/l 语言编写的,但下面的讨论整体上是与编程语 言无关的;这些技术可以用在其他语言编写的程序中。同时,由于本模块中的 PL/1 逻辑结构非常简单,实际上任何读者,甚至不熟悉 PL/l 的人也应该能够读懂程序。
2 这三个数据与图 5-2 源程序中的数据不一致,图是$200,$100。——译者注。
BONUS : PROCEDURE(EMPTAB,DEPTTAB,ESIZE,DSIZE,ERRCODE); DECLARE 1 EMPTAB (*),
2 NAME CHAR(6),
2 CODE CHAR(1),
2 DEPT CHAR(3),
2 SALARY FIXED DECIMAL(7,2);
DECLARE 1 DEPTTAB (*),
2 DEPT CHAR(3),
2 SALES FIXED DECIMAL(8,2);
DECLARE (ESIZE,DSIZE) FIXED BINARY; DECLARE ERRCODE FIXED DECIMAL(1);
DECLARE MAXSALES FIXED DECIMAL(8,2) INIT(0); /*MAX. SALES IN DEPTTAB*/
DECLARE (I,J,K) FIXED BINARY; /*COUNTERS*/
DECLARE FOUND BIT(1); /*TRUE IF ELIGIBLE DEPT. HAS EMPLOYEES*/ DECLARE SINC FIXED DECIMAL(7,2) INIT(200.00); /*STANDARD INCREMENT*/ DECLARE LINC FIXED DECIMAL(7,2) INIT(100.00); /*LOWER INCREMENT*/ DECLARE LSALARY FIXED DECIMAL(7,2) INIT(15000.00); /*SALARY BOUNDARY*/
DECLARE MGR CHAR(1) INIT('M');
1 ERRCODE=0;
2 IF(ESIZE<=0)|(DSIZE<=0)
3 THEN ERRCODE=1; /*EMPTAB OR DEPTTAB ARE EMPTY*/
4 ELSE DO;
5 DO I = 1 TO DSIZE; /*FIND MAXSALES AND MAXDEPTS*/
6 IF(SALES(I)>=MAXSALES) THEN MAXSALES=SALES(I);
7 END;
8 DO J = 1 TO DSIZE;
9 IF(SALES(J)=MAXSALES) /*ELIGIBLE DEPARTMENT*/
10 THEN DO;
11 FOUND='0'B;
12 DO K = 1 TO ESIZE;
13 IF(EMPTAB.DEPT(K)=DEPTTAB.DEPT(J))
14 THEN DO;
15 FOUND='1'B;
16 IF(SALARY(K)>=LSALARY)|CODE(K)=MGR)
17 THEN SALARY(K)=SALARY(K)+LINC;
18 ELSE SALARY(K)=SALARY(K)+SINC;
19 END;
20 END;
21 IF(-FOUND) THEN ERRCODE=2;
22 END;
23 END;
24 END;
25 END;
图 5-2 BONUS 模块
无论采用哪种逻辑覆盖方法,第一步都是要列举出程序中所有的条件判断。该 程序中需要列出的对象是所有的 IF 和 DO 语句。通过对程序进行检查,我们可以看 出所有的 DO 语句都是此简单的迭代,每一个迭代的上限都等于或大于初始值(意
因此,该程序中的 DO 语句无须特别关注,因为任何导致 DO 语句执行的测试用例
最终都会使其进入两个方向的分支路径(即进入循环体和退出循环体)。因此,必 须要进行分析的语句有:
2 IF (ESIZE<=O) | (DSIZE<=0)
6 IF (SALES(I) >= MAXSALES)
9 IF (SALES(J) = MAXSALES)
13 IF (EMPTAB.DEPT(K) = DEPTTAB.DEPT(J))
16 IF (SALARY(K) >= LSALARY) | (CODE(K) =MGR)
21 IF(-FOUND) THEN ERRCODE=2
得到了数量较少的判断后,我们可能会选择多重条件覆盖,但应该检查所有的
逻辑覆盖准则(语句覆盖准则除外,其限制太多以至于不易于使用),看看它们的 效果如何。为了满足判定覆盖准则,我们需要设计充足的测试用例,来触发上述 6 个判断中每一个的全部输出结果。触发所有判定输出所需的输入状态列举在表 5-1 中。由于有两个输出结果总会发生,故需要测试用例触发的状态只有 10 个。注意, 要建立表 5-l ,必须沿程序逻辑结构对判定输出的状态进行回溯,以判别相应的正 确输入状态。例如,判断 16 不会被任何符合条件的雇员触发。雇员必须处在满足 条件的部门之内。
表 5-1 与判定输出对应的状态
Decision
True Outcome
False Outcome
2
ESIZE or DSIZE ≤ 0.
ESIZE and DSIZE > 0.
6 Will always occur at least once. Order DEPTTAB so that a
department with lower sales occurs after a department with higher sales.
9 Will always occur at least once All departments do not have the same sales.
13 There is an employee in an eligible department.
16 An eligible employee is either a manager or earns LSALARY or more.
21 All eligible departments contain no
There is an employee who is not in an eligible department.
An eligible employee is not a manager and earns less than LSALARY.
An eligible department contains at
employees. least one employee.
表 5-l 中关注的 10 种情况可以被图 5-3 中所示的两个测试用例触发。注意,
每个测试用例都包含了对预期输出的定义,这是符合本书第 2 章讨论的测试原则 的。
测试
用例
输入
预期的输出
1
ESIZE = 0
All other inputs are irrelevant
ERRCODE = 1
ESIZE, DSIZE, EMPTAB, and
DEPTTAB are unchanged
2
ESIZE = DSIZE = 3
EMPTAB
DEPTTAB
ERRCODE = 2
ESIZE, DSIZE, and DEPTTAB are unchanged
EMPTAB
JONES
E
D42
21,000.00
SMITH
E
D32
14,000.00
LORIN
E
D42
10,000.00
D42
10,000.00
D32
8,000.00
D95
10,000.00
JONES
E
D42
21,100.00
SMITH
E
D32
14,000.00
LORIN
E
D42
10,200.00
图 5-3 满足判定覆盖准则的测试用例
尽管这两个测试用例都满足判定覆盖准则,但很明显,该模块中仍可能存在着 很多类型的错误不能通过这两个用例来发现。举例来说,这两个用例没有对错误代 码为 0、某名雇员是管理人员或部门表为空(DSIZE<=0)时的情况进行检查。
JONES
E
D42
21,000.00
SMITH
E
D32
14,000.00
LORIN
E
D42
10,000.00
D42
10,000.00
D32
8,000.00
D95
10,000.00
使用条件覆盖准则可以获得更为满意的测试效果。因此我们需要设计出足够的 测试用例,来触发判断中每个条件的所有输出结果。触发所有输出结果的条件以及 所需的输入情况列在表 5-2 中。由于两个输出结果总会出现,需要测试用例强制触 发的状态有 14 个。这些状态同样可以仅被两个测试用例触发,如图 5-4 所示。
测试
用例
输入
预期的输出
1
ESIZE = DSIZE = 0
All other inputs are irrelevant
ERRCODE = 1
ESIZE, DSIZE, EMPTAB, and
DEPTTAB are unchanged
2
ESIZE = DSIZE = 3
EMPTAB
DEPTTAB
ERRCODE = 2
ESIZE, DSIZE, and DEPTTAB are unchanged
EMPTAB
JONES
E
D42
21,100.00
SMITH
E
D32
14,000.00
LORIN
E
D42
10,200.00
图 5-4 满足条件覆盖准则的测试用例
图 5-4 所示的测试用例是用来说明某个问题的。由于他们的确可以触发表 5-2
75
效果来看,它们要比图 5-3 中的测试用例集差一些。原因是它们没有执行到每条语
句。举例来说,语句 18 就从没有被执行过。而且,它们也不比图 5-3 中的测试用例 更全面,它们没有触发输出 ERRORCODE=0。如果语句 2 错误地编写成(ESIZE=0) 并且(DSIZE=0),这个错误就检查不出来。当然,其他的测试用例集也可能会解 决这个问题,但是图 5-4 中的两个测试用例确实可以满足条件覆盖准则,这个事实 是存在的。
使用判断/条件覆盖准则可以克服图 5-4 中的测试用例存在的明显缺陷。这里, 我们会提供足够多的测试用例,使得所有条件和判断的全部输出结果都至少被触发 一次。让 Jones 当管理人员,Lorin 当非管理人员,就可以实现这一点。这样做的结 果是,将产生判断 16 的两个输出结果,从而使语句 18 得到执行。
然而,这样做存在一个问题,从根本上讲它并不比图 5-3 中的测试用例更完善。 如果我们使用的编译器一旦判断出“或”表达式中某个操作对象结果为真,就停止 检查该表达式,那么就会导致语句 16 中 CODE(K)=MGR 表达式永远得不到为真的 结果。因此,如果该表达式编码不正确,这些测试用例无法发现此错误。
我们要讨论的最后一个准则式多重条件覆盖准则。这个准则要求设计出足够多 的测试用例,以便将每个判断中的所有可能的条件组合至少触发一次。这项工作可 以从表 5-2 开始。判断 6、9、13 和 21 每个都有两种组合;而判断 2 和 16 每个都有
4 种组合。设计测试用例的方法是挑选出一个测试用例覆盖到尽可能多的组合情况, 然后再挑选出一个测试用例覆盖到尽可能多的剩余组合情况,以此类推。满足多重 条件覆盖准则的测试用例集如图 5-5 所示。这个集合要比前面的测试用例集全面得 多,也就意味着,我们应该开始就选择多重条件覆盖准则。
表 5-2 与条件输出对应的状态
Decision
Condition
True Outcome
False Outcome
2
ESIZE ≤ 0
ESIZE ≤ 0
ESIZE > 0
2
DSIZE ≤ 0
DSIZE ≤ 0
DSIZE > 0
6
SALES (I) ≥MAXSALES
Will always occur at least once.
Order DEPTTAB so that a department With lower sales occurs after a department with higher sales.
9
SALES (J) = MAXSALES
Will always occur at least
All departments do not have the same
once.
sales.
13
EMPTAB.DEPT(K) = DEPTTAB.DEPT(J)
There is an employee
There is an employee who in an eligible is not in an department. Eligible department.
16
SALARY (K)
≥LSALARY
An eligible employee earns
LSALARY or more.
An eligible employee earns less than
LSALARY.
16
CODE (K) = MGR
An eligible employee is a manager.
An eligible employee is not a manager.
21
-FOUND
An eligible department contains no employees.
An eligible department contains at least one employee.
测试
用例
输入
预期的输出
1
ESIZE = 0 DSIZE = 0
All other inputs are irrelevant
ERRCODE = 1
ESIZE, DSIZE, EMPTAB, and
DEPTTAB are unchanged
2
ESIZE = 0 DSIZE > 0
All other inputs are irrelevant
Same as above
3
ESIZE > 0 DSIZE = 0
All other inputs are irrelevant
Same as above
4
ESIZE =5 DSIZE = 4
EMPTAB
DEPTTAB
ERRCODE = 2
ESIZE, DSIZE, and DEPTTAB
are unchanged
EMPTAB
JONES
M
D42
21,100.00
WARNS
M
D95
12,100.00
LORIN
E
D42
10,200.00
TOY
E
D95
16,100.00
SMITH
E
D32
14,000.00
JONES
M
D42
21,000.00
WARNS
M
D95
12,000.00
LORIN
E
D42
10,000.00
TOY
E
D95
16,000.00
SMITH
E
D32
14,000.00
D42
10,000.00
D32
8,000.00
D95
10,000.00
D44
10,000.00
图 5-5 满足多重条件覆盖准则的测试用例
BONUS 模块可能存在着大量的错误,即使是满足多重条件覆盖准则的测试用 例也检查不出来,认识到这一点很重要。举例来说,没有测试用例会触发 ERRORCODE 返回值为 0 的情况,因此,如果语句 1 漏掉了,该错误就发现不到。 如果 LSALARY 被错误地初始化为$150,000.01,这个错误也将无法发现。如果语句
16 中写的是 SALARY(K)> LSALARY 而不是 SALARY(K)>= LSALARY,这个错误
也将无法发现。同样,各种 off-by-one 错误(例如没有正确地处理 DEPTTAB 或
EMPTAB 表中最末的条目)能否检查出来,在很大程度上全凭运气。 现在有两个观点清晰了:多重条件覆盖准则优先于其它准则;任何逻辑覆盖准
则尚不足以胜任作为生成模块测试用例的惟一手段。因此,下一个步骤就是用一组
黑盒测试用例来补充图 5-5 中的测试用例。要做到这一点,我们将 BONUS 模块的 接口规格说明列举在下面:
PL/1 模块 BONUS 接收 5 个参数,分布是 EMPTAB,DEPTTAB、ESIZE、DSIZE
和 ERRORCODE。这些参数的属性如下:
DECLARE 1 EMPTAB(*), /*INPUT AND OUTPUT*/
2 NAME CHARACTER(6),
2 CODE CHARACTER(1),
2 DEPT CHARACTER(3),
2 SALARY FIXED DECIMAL(7,2); DECLARE 1 DEPTTAB(*), /*INPUT*/
2 DEPT CHARACTER(3),
2 SALES FIXED DECIMAL(8,2);
DECLARE (ESIZE, DSIZE) FIXED BINARY; /*INPUT*/
DECLARE ERRCODE FIXED DECIMAL(1); /*OUTPUT*/
模块假设传递的参数具有以上属性。ESIZE,DSIZE 分别表示 EMPTAB、DEPTTAB
表中的条目数量,而 EPTAB、DEPTTAB 表中的条目顺序未作任何假设。该模块的功 能是为销售额(DEPTTAB、SALES)最高的一个或多个部门的雇员增加薪水(EMPTAB, SALARY)。如果某个符合条件的雇员当前工资已经达到或超过了$150,000,或者该 雇员为管理人员(EMPTAB.CODE=‘M’),则薪水只增加$1,000,否则增加$2,000。 模块假设增加的薪水放入 EMPTAB.SALARY 中。如果 ESIZE、DSIZE 不大于 0,则将 ERRCODE 设置为 1,并且不进行任何操作。在所有其他情况下,完整地执行模块的 功能。然而,如果某个具有最大销售额的部门没有雇员,程序继续执行,但将 ERRCODE 设置为 2;否则设置为 0。
上述规格说明并不适合于因果图分析方法(没有一组应检查其组合情况的能力 分辨出来的输入条件),因此采用边界值分析方法。确定的输入边界如下:
1. EMPTAB 中条目数量为 1。
2. EMPTAB 中条目数量为最大值(65 535)。
3. EMPTAB 中条目数量为 0。
4. DMPTAB 中条目数量为 1。
5. DMPTAB 中条目数量为最大值(65 535)。
6. DMPTAB 中条目数量为 0。
7. 某个销售额最高的部门仅有 1 名雇员。
8. 某个销售额最高的部门有 65535 名雇员。
9. 某个销售额最高的部门没有雇员。
10. DEPTTAB 中所有部门的销售额相同。
11. 销售额最高的部门为 DEPTTAB 的第一条目。
12. 销售额最高的部门为 DEPTTAB 的最末条目。
13. 某个符合条件的雇员为 EMPTTAB 的第一条目。
14. 某个符合条件的雇员为 EMPTTAB 的最末条目。
15. 某个符合条件的雇员为管理人员。
16. 某个符合条件的雇员不是管理人员。
17. 某个不为管理人员、且符合条件的雇员的薪水为$149 999.99。
18. 某个不为管理人员、且符合条件的雇员的薪水为$150 000。
19. 某个不为管理人员、且符合条件的雇员的薪水为$150 000.01。 输出边界如下:
20. ERRCODE=0。
21. ERRCODE=1。
22. ERRCODE=2。
23. 某个符合条件雇员增加后的薪水为$299 999.99。
使用错误猜测技术进一步确定的测试条件如下:
24. 在 DEPTTAB 中,某个没有雇员的销售额最大的部门其后跟着另一个有雇 员的销售额最大的部门。
这用来判断当遇到 ERRCODE=2 的情况时,模块是否错误地终止对输入的处
理。
评价一下这 24 种条件,其中条件 2、5 和 8 看起来像是不切实际的测试用例。
由于它们所代表的条件不可能发生(在测试中作这种假设通常是很危险的,但在此 处似乎比较安全),因此可以将它们排除掉。下一步是将剩下的 21 种条件与当前的 测试用例集(图 5-5)进行比较,判断哪些边界条件尚未被覆盖到。通过比较,我
们发现需要为条件 l、4、7、10、14、17、18、19、20、23 和 24 设计图 5-5 中没有
的测试用例。
再下一步是设计额外的测试用例以覆盖上述 11 种边界条件。一种方法是将这 些条件合并到现有的测试用例中去(即对图 5-5 中的第 4 个用例进行修改),但我们 不推荐这样做,因为这样做会打乱现有测试用例完整的多重条件覆盖。因此,最保 险的方法是增加图 5-5 之外的测试用例。在这样做的过程中,我们的目标是使设计 覆盖边界条件所需的最小数量的测试用例。图 5-6 中的三个测试用例实现了这一点。 测试用例 5 覆盖了条件 7、10、14、17、18、19 和 20,测试用例 6 覆盖了条件 l、4 和 23,而测试用例 7 则覆盖了条件 24。
ALLY
E
D36
14,999.99
BEST
E
D33
15,000.00
CELTO
E
D33
15,000.01
D33
55,400.01
D36
55,400.01
CHIEF
M
D99
98,899.99
DOLE
E
D67
10,000.00
WARNS
E
D22
33,333.33
D66
20,000.00
D67
20,000.00
在这里我们有理由相信,逻辑覆盖准则或白盒测试用例、图 5-6 所示的测试用 例已实现对模块 BONUS 适度的模块测试。
测试
用例
输入
预期的输出
5
ESIZE = 3 DSIZE = 2
EMPTAB
DEPTTAB
ERRCODE = 0
ESIZE, DSIZE, and DEPTTAB
are unchanged
EMPTAB
ALLY
E
D36
15,199.99
BEST
E
D33
15,100.00
CELTO
E
D33
15,100.01
4
ESIZE =1 DSIZE = 1
EMPTAB
DEPTTAB
D99 99,000.00
ERRCODE = 0
ESIZE, DSIZE, and DEPTTAB
are unchanged
EMPTAB
CHIEF
M
D99
99,899.99
4
ESIZE =2 DSIZE = 2
EMPTAB
DEPTTAB
ERRCODE = 2
ESIZE, DSIZE, and DEPTTAB
are unchanged
EMPTAB
DOLE
E
D67
10,000.00
WARNS
E
D22
33,333.33
图 5-6 BONUS 模块补充的边界值分析测试用例
5.2 增量测试
在执行模块测试过程中,我们主要有两点考虑:第一,如何设计一个有效的测试 用例集,这在上一节已经讨论过。第二,将模块组装成工作程序的方式。第二点考
虑很重要,因为它涉及模块测试用例编写的形式、可能用到的测试工具类型、模块
编码和测试的顺序、生成测试用例的成本以及调试(定位并修复检查出的错误)的 成本等。简而言之,它具有实际重要性。在这一节中,我们将讨论两类方法,增量 测试和非增量测试。在下一节中,我们将探讨两种增量方法:自顶向下的和自底向 上的开发或测试过程。
这里需要考虑的问题是:软件测试是否应先独立地测试每个模块,然后再将这 些模块组装成完整的程序?还是先将下一步要测试的模块组装到测试完成的模块 集合中,然后再进行测试?第一种方法称为非增量测试或“崩溃(big-bang ) ”测 试,而第二种方法称为增量测试或集成。
图 5-7 所示的程序可作为一个例子。矩形框代表程序的 6 个模块(子程序或过 程),连接模块间的线条代表程序的控制层次,也就是说,模块 A 调用模块 B、C 和 D,模块 B 调用模块 E 等等。作为传统方法的作增量测试是按如下方式进行的: 首先,对 6 个模块中的每一个模块进行单独的模块测试,将每个模块视为一个独立 实体。根据环境(例如,是人机交互式的,还是使用批处理计算工具)和参与人数, 这些模块可以同时或按次序进行测试。最后,将这些模块组装或集成(例如“连接 编辑”)为完整的程序。
图 5-7 包含 6 个模块的程序范例
测试单独的模块需要一个特殊的驱动模块(drive module)和一个或多个桩模块
(stub module)。举例来说,测试模块 B,首先要设计测试用例,然后将测试用例作
为输入参数由驱动模块传递给模块 B。驱动模块是人们编写的一个小模块,用来将
测试用例驱动或传输到被测模块中(也可以用测试工具替代)。驱动模块还必须向 测试人员显示模块 B 的结果。此外,由于模块 B 调用了模块 E,所以还必须使用一 个额外的组件,该组件在模块 B 调用模块 E 时接受模块 B 的控制指令。这就由桩 模块来完成它是一个被命名为“E”的特殊模块,用来模拟模块 E 的功能。当所有
6 个模块的模块测试都完成之后,就将这些模块组装成完整的程序。 另一种可选择的方法是增量测试。不同于独立地测试每个模块,增量测试首先
将下一个要测试的模块组装到前面已经测试过的模块集合中去。
现在要给出对图 5-7 所示的程序进行增量测试的步骤还为时太早,因为还有大 量可能的增量方法。一个关键问题是我们究竟是从程序的顶部开始、还是从底部开 始进行测试。由于这个问题将在下一节中讨论,我们暂且假设从底部开始测试。第 二步先测试模块 E、C 和 F,可以并行测试(由三个人进行),也可串行进行。请注 意,我们必须要为每个模块准备一个驱动模块,但不是桩模块。下一步是测试模块
B 和 D,但不是单独地测试它们,而是分别将其与模块 E 和 F 组装在一起。换言之, 要测试模块 B,应编写驱动模块和集成测试用例,将模块 B 和 E 组合起来测试.将 下一个要测试的模块组装到前面已经测试过的模块集合或子集中去,这个增长的过 程会一直进行到测试完最后一个模块(本例中是模块 A)为止,请注意,这个过程 也可以自顶向下进行。下面是几个显而易见的结论:
1. 非增量测试所需的工作量要多一些。对于图 5-7 所示的程序,需要准备 5 个驱动模块和 5 个桩模块(假设顶部的模块不需要驱动模块)。自底向上的 增量测试需要 5 个驱动模块,但不需要桩模块。自顶向下的增量测试需要
5 个桩模块,但不需要驱动模块。增量测试所需的工作量要少一些,因为 使用了前面测试过的模块来取代非增量测试中所需要的驱动模块(如果从 顶部开始测试)或桩模块(如果从底部开始测试)。
2. 如果使用了增量测试,可以较早地发现模块中与不匹配接口、不正确假设 相关的编程错误。这是由于尽早地对模块组合进行了集成测试。然而,如 果采用非增量测试,只有到了测试过程的最后阶段,模块之间才能“互相 看到”。
3. 因此如果使用了增量测试,调试会进行得容易一些,我们假定存在着与模
块间接口或假设相关的编程错误(根据经验而来的合理假设),那么,如果
使用非增量测试,直到整个程序组装之后,这些错误才会浮现出来。到了 这个时候,我们就难以定位错误。因为它可能存在于程序内部的任何位置, 相反,如果使用增量测试,这种类型的错误就很容易发现,因为该错误很 可能与最近添加的模块有关。
4. 增量测试会将测试进行得更彻底。如果当前正在测试模块 B,要么是模块 E,要么是模块 A(取决于测试是从底部还是从顶部开始的)被当作结果而 执行。虽然模块 E 或模块 A 先前已经进行了完全的测试,但将其作为 B 的 模块测试结果而执行,则会诱发出一个新的情况,可能会暴露出先前测试 过的模块 E 或模块 A 中存在的一个新缺陷。另一方面,如果使用的是非增 量测试,对模块 B 的测试仅影响到其本身。换言之,增量测试使用先前测 试过的模块,取代了非增量测试中使用的桩模块或驱动模块。因此,到最 后一个模块测试完成时,实际的模块经受到了更多的检验。
5. 非增量测试所占用的机器时间显得少一些。如果使用自底向上的方法测试 图 5-7 中的模块 A,在执行 A 的过程中,模块 B、C、D、E 和 F 也会执行 到。而在对模块 A 的非增量测试中,仅会执行模块 B、C 和 E 的桩模块。 自顶向下的增量测试的情况也是如此。如果测试的是模块 F,那么在执行 模块 F 时还会执行模块 A、B、C、D 和 E;而在对模块 F 的非增量测试中, 仅有模块 F 的驱动模块与其一起执行。因此,完成一次增量测试所需执行 的机器指令,显然多于采用非增量测试方法所需的指令。但此消彼长的是, 非增量测试要比增量测试需要更多的驱动模块和桩模块,开发这些驱动模 块和桩模块是要占用机器时间的。
6. 模块测试阶段开始时,如果使用的是非增量测试,就会有更多的机会进行 并行操作(也就是说,所有的模块可以同时测试)。对于大型的软件项目〔模 块和人员都很多),这可能十分重要,因为在模块测试开始之时,项目的人 员数量常常处于最高峰。
总的来说,第 1 条~第 4 条结论是增量测试的优点,而第 5、6 条结论是其不 利之处。考虑到计算机行业当前的趋势(硬件成本已经降低而且势必会持续下去, 硬件的功能不断增加,而人力劳动成本和软件错误的代价在不断增长),再考虑到
错误发现得越早,改正它的成本也越低,我们会看到第 1 条至第 4 条结论的重要性
日益突出,而第 5 条结论越来越显得不那么重要。如果有一个缺点的话,第 6 条结 论似乎确是一个薄弱的缺点。从而我们可以得出结论,增量测试要更好一些。
5.3 自顶向下测试与自底向上测试
在上一节结论的基础上,即增量测试要优于非增量测试,本节将讨论两种增量 测试策略:自顶向下的测试和自底向上的测试。然而在讨论它们之前,先要澄清几 个误解。
首先,“自顶向下的测试”、“自顶向下的开发”和“自顶向下的设计”常用作近 义词。“自顶向下的测试”和“自顶向下的开发”确实是同义词(表示安排模块的 编码和测试顺序的策略),但“自顶向下的设计”则完全不同并且是独立的概念, 按自顶向下模式设计的程序既可使用自顶向下的方式,也可使用自底向上的方式进 行增量测试。
其次,自底向上的测试(或自底向上的开发)常被错误地当作非增量测试。原 因在于自底向上的测试的开展方式与非增量测试是相同的(即对底层或终端模块进 行测试),但是就如我们从上一节看到的那样,自底向上的测试是一种增量测试。
最后,由于两种策略都属于增量测试,因此增量测试的优点在这里就不再赘述, 仅讨论自顶向下测试与自底向上测试的差异。
5.3.1 自顶向下的测试
自顶向下的测试是从程序的顶部或初始模块开始。测试开始之后,挑选哪
展开阅读全文