资源描述
测试用例的设计
除了第 2 章探讨的软件测试的心理学问题以外,软件测试中最重要的因素是设 计和生成有效的测试用例。
然而,无论软件测试进行得如何具有创造性、如何完全,也不能保证软件中不 存在任何错误。测试用例的设计如此重要,原因在于完全的测试是不可能的,对任 何程序的测试必定是不完全的。那么,最显然的测试策略就是努力使测试尽可能完 全。
由于时间和成本的约束,软件测试的最关键问题是: 在所有可能的测试用例中,哪个子集最有可能发现最多的错误?
对软件测试用例设计方法的研究为这个问题提供了答案。 一般而言,在所有的方法中效率最低的是随机输入测试,即在所有可能的输入
值中随机选取某个子集来对程序进行测试的过程。就发现最多错误的可能性而言,
随机选取而产生的测试用例集很少有可能是理想的或接近理想的子集。在本章中, 我们将提出一套思考过程,该过程有助于更加睿智地选取测试数据。
本书第 2 章已经证明穷举的黑盒和白盒测试通常都是不可能的,但同时也建议: 将这两种测试的要素组合起来得到一种合理的测试策略。本章将对这种策略进行研 究。我们可以通过使用特定的面向黑盒测试的测试用例设计方法,而后使用白盒测 试方法对程序的逻辑结构进行检查以补充这些测试用例,借此来设计出一个相当严 格的测试。
本章将要讨论的测试方法如下: 黑盒测试 白盒测试
等价类划分 语句覆盖 边界值分析 判定覆盖 因果图分析 条件覆盖 错误测试 判定/条件覆盖
多重条件覆盖
尽管上述方法将分开来进行讨论,但我们建议综合最多的(如果不能是全部的
话)测试方法来设计严格的程序测试,因为每一种测试方法都有其独特的优势和弱 点。举例来说,某种方法遗漏掉的错误,而用其他的方法就可能找出来。
没有人曾承诺说:软件测试会是容易的事。引用一位智者的话,“如果你觉得 设计和编写程序很困难,你就并非一无所知。”
我们推荐的步骤是先使用黑盒测试方法来设计测试用例,然后视情况需要使用 白盒测试方法来设计补充的测试用例。下面首先讨论较为有名的白盒测试方法。
4.1 白盒测试(White-Box Testing)
4.1.1 逻辑覆盖测试(Logic-Coverage Testing)
白盒测试关注的是测试用例执行的程度或覆盖程序逻辑结构(源代码)的程度。 如同我们在本书第 2 章所看到的,完全的白盒测试是将程序中每条路径都执行到, 然而对一个带有循环的程序来说,完全的路径测试并不切合实际。
图 4-1 被测试的小程序
如果完全从路径测试中跳出来看,那么有价值的目标似乎就是将程序中的每条 语句至少执行一次。遗憾的是,这恰是合理的白盒测试中较弱的准则。图 4-1 描述
了这种思想。假设图 4-l 代表了一个将要进行测试的小程序,其等价的 Java 代码
段如下:
Public void foo(int a, int b, int x) {
if (a>1 && b == 0) {
x=x/a;
}
if (a==2 || x>1) {
x=x+1;
}
}
通过编写单个的测试用例遍历程序路径 ace,可以执行到每一条语句。也就是 说,通过在点 a 处设置 A=2,B=0,X=3,每条语句将被执行一次(实际上,X 可 被赋任何值)。
遗憾的是,这个准则相当不足。举例来说,也许第一个判断应是“或”,而不 是“与”。如果这样,这个错误就会发现不到。另外,可能第二个判断应该写成“X>0”, 这个错误也不会被发现。还有,程序中存在一条 X 未发生改变的路径(路径 abd), 如果这是个错误,它也不会被发现。换句话说,语句覆盖这条准则有很大的不足, 以至于它通常没有什么用处。
判定覆盖或分支覆盖是较强一些的逻辑覆盖准则。该准则要求必须编写足够的 测试用例,使得每一个判断都至少有一个为“真”和为“假”的输出结果。换句话 说,也就是每条分支路径都必须至少遍历一次。分支或判定语句的例子包括 switch,do-while 和 if-else 语句.在一些程序语言如 FORTRAN 中,多重选 择 GOTO 语句也是合法的。
判定覆盖通常可以满足语句覆盖。由于每条语句都是在要么从分支语句开始, 要么从程序入口点开始的某条子路径上,如果每条分支路径都被执行到了,那么每 条语句也应该被执行到了。但是,仍然还有至少三种例外情况:
• 程序中不存在判断。
• 程序或子程序/方法有着多重入口点。只有从程序的特定入口点进入时,某 条特定的语句才能执行到。
• 在 ON 单元(ON-unit)里的语句。遍历每条分支路径并不一定能确保所有 的 ON 单元都能执行到。由于我们将语句覆盖视为一个必要条件,那么,
作为似乎更佳准则的判定覆盖的定义理应涵盖语句覆盖。因此,判定覆盖
要求每个判断都必须有“是”和“否”的结果,并且每条语句都至少被执 行一次。换一种更简单的表达方式,即每个判断都必须有“是”和“否” 的结果,而且每个入口点(包括 ON 单元)都必须至少被调用一次。
我们的探讨仅针对有两个选择的判断或分支,当程序中包含有多重选择的判断 时,判定/分支覆盖准则的定义就必须有所改变。典型的例子有包含 select(case) 语句的 Java 程序,包含算术(三重选择)IF 语句、计算或算术 GOTO 语句的 FORTRAN 程序,以及包含可选 GOTO 语句或 GO-TO-DEFENDING-ON 语句的 COBOL 程序。对于这 些程序,判定/分支覆盖准则将所有判断的每个可能结果都至少执行一次,以及将 程序或子程序的每个入口点都至少执行一次。
在图 4-1 中,两个涵盖了路径 ace 和 abd,或涵盖了路径 acd 和 abe 的测试用 例就可以满足判定覆盖的要求。如果我们选择了后一种情况,两个测试用例的输入 是 A=3,B=0,X=3 和 A=2,B=1,X=1。
判定覆盖是一种比语句覆盖更强的准则,但仍然相当不足。举例来说,我们仅 有 50%的可能性遍历到那条 X 未发生变化的路径(也即,仅当我们选择前一种情况)。 如果第二个判断存在错误(例如把 X>1 写成了 X<1,那么前面例子中的两个测试用 例都无法找出这个错误。
比判定覆盖更强一些的准则是条件覆盖。在条件覆盖情况下,要编写足够的测 试用例以确保将一个判断中的每个条件的所有可能的结果至少执行一次。因为,就 如同判定覆盖的情况一样,这并不总是能让每条语句都执行到,因此作为对这条准 则的补充就是对程序或子程序,包括 ON 单元的每一个入口点都至少调用一次。举 例来说,分支语句
DO K=0 to 50 WHILE (J+K<QUEST)
包含两种情况:K 是否小于或等干 50?以及 J+K 是否小于 QUEST? 因此,需 要针对 K <= 50、K >50(达到循环的最后一次迭代)以及 J+K<QUEST、J+K>=QUEST 的情况设计测试用例。
图 4-1 有四个条件:A>1、B=0、A=2 以及 X>1。因此需要足够的测试用例,使 得在点 a 处出现 A=2、A<2、X>1 及 X<=1 的情况。有足够数量的测试用例满足此准
则,用例及其遍历的路径如下所示:
1.A=2,B=0,X=4 ace
2.A=1,B=1,X=1 adb
请注意,尽管在本例中生成的测试用例数量是一样的,但条件覆盖通常还是要 比判定覆盖更强一些。因为,条件覆盖可能(但并不总是这样)会使判断中的各个 条件都取到两个结果(“真”和“假”),而判定覆盖却做不到这一点。举例来说, 在相同的分支语句
DO K=0 to 50 WHILE (J+K<QUEST)
中,存在一个两重分支(执行循环体,或者跳过循环体)。如果使用的是判定 覆盖测试,将循环从 K = 0 执行到 K = 51 即可满足该准则,但从未考虑到 WHILE 子句为假的情况。如果使用的是条件覆盖准则,就需要设计一个测试用例为 J+K<QUEST 产生一个为假的结果。
虽然条件覆盖准则乍看上去似乎满足判定覆盖准则,但并不总是如此。如果正 在测试判断条件 IF (A&B),条件覆盖准则将要求编写两个测试用例:A 为真,B 为假;A 为假,B 为真。但是这并不能使 IF 语句中的 THEN 被执行到。对图 4-1 所
示例子所进行的条件覆盖测试涵盖了全部判断结果,但这仅仅是偶然情况。举例来 说,两个可选的测试用例:
1.A=2,B=0,X=3
2.A=1,B=1,X=1 涵盖了全部的条件结果,却仅涵盖了四个判断结果中的两个(这两个测试用例都涵
盖到了路径 abe,因而不会执行第一个判断结果为真的路径,以及第二个判断结果
为假的路径)。 显然,解决上面左右为难局面的办法就是所谓的判定/条件覆盖准则。这种准则
要求设计出充足的测试用例。将一个判断中的每个条件的所有可能的结果至少执行
一次,将每个判断的每个条件的所有可能的结果至少执行一次,将每个判断的所有 可能的结果至少执行一次,将每个入口点都至少调用一次。
判定/条件覆盖准则的一个缺点是尽管看上去所有条件的所有结果似乎都执行
到了,但由于有些特定的条件会屏蔽掉其他的条件,常常并不能全部都执行到。请
参见图 4-2 来观察此种情况。图 4-2 中的流程图描述的是编译器将图 4-l 中的程序 编译生成机器代码的过程。源程序中的多重条件判断被分解成单个的判断和分支, 因为大多数的机器都没有能执行多重条件判断的单独指令。那么,更为完全的测试 覆盖似乎是将每个基本判断的全部可能的结果都执行到,而前两个判定覆盖的测试 用例都做不到这点,它们未能执行到判断 H 中的结果为“假”的分支,以及判断 K 中 结果为“真”的分支。
H
A>1
Y
N
I B=0
Y
N
X=X/A
J
A=2
Y
N
K
X>1
Y
N
d
X=X+1
图 4-2 图 4-1 中程序的机器码
如图 4-2 所示,其中的原因是“与”和“或”表达式中某些条件的结果可能会 屏蔽掉或阻碍其他条件,的判断。举例来说,如果“与”表达式中有个条件为“假”, 那么就无须计算该表达式中的后续条件。同样,如果“或”表达式中有个条件为“真”, 那么后续条件也无须计算。因此,条件覆盖或判定/条件履盖谁则不一定会发现逻 辑表达式中的错误。
所谓的多重条件覆盖准则能够部分解决这个问题。该准则要求编写足够多的测
试用例,将每个判定中的所有可能的条件结果的组合,以及所有的入口点都至少执
行一次。举例来说,考虑下面的伪代码程序;
NOTFOUND=TRUE;
DO I=1 TO TABSIZE WHILE (NOTFOUND); /*SEARCH TABLE*/
……searching logic……; END
要测试四种情况:
1. I<=TABSIZE,并且 NOTFOUND 为真;
2. I<=TABSIZE,并且 NOTFOUND 为假(在到达表格尾部前查询指定条目);
3. I>TABSIZE,并且 NOTFOUND 为真(查询了整个表格,未找到指定条目);
4. I>TABSIZE,并且 NOTFOUND 为假(指定条目位于表格的最后位置)。 很容易发现,满足多重条件覆盖准则的测试用例集,同样满足判定覆盖准则、
条件覆盖准则以及判定/条件覆盖淮则。
再次回到图 4-l 中,测试用例必须覆盖以下 8 种组合:
1. A>1,B=0
2. A>1,B<>0
3. A<=1,B<>0
4. A=2,X>1
5. A=2,X<=1
6. A<>2,X>1
7. A<>2,X<=1
注意,与左边的情况一样,第 5 至第 8 组合表示了第二个 if 语句的值。由于 X 可能在该 if 语句之前发生了改变,因此这个 if 语所需的值必需对程序逻辑进行回 溯,以找到相对应的输入值,要测试的这 8 种组合并不一定意味着需要设计出 8 个 测试用例。实际上,用 4 个测试用例就可以覆盖它们。下面是这些测试用例的输入, 以及它们覆盖的组合:
A=3,B=0,X=4
覆盖组合 1,5
A=2,B=1,X=1
覆盖组合 2,6
A=1,B=0,X=2
覆盖组合 3,7
A=1,B=1,X=1
覆盖组合 4,8
图 4-1 的程序存在 4 条不同的路径,需要 4 个测试用例,这样的情况纯属
巧合。事实上,这 4 个用例也没有覆盖到每条路径,路径 acd 就被遗漏掉了。 举例来说,对于如下所示的判断语句,尽管它只包舍两条路径,仍可能需要 8 个测试用例:
if (x==y && length(z)==0 && FLAG) {
j=1 else
i=1;
}
在存在循环的情况下,多重条件覆盖准则所需要的测试用例的数量通常会远远 小于其路径的数量。
总的来说,对于包含每个判断只存在一种条件的程序,最简单的测试准则就是 设计出足够数量的测试用例,实现:(1)将每个判断的所有结果都至少执行一次;
(2)将所有的程序入口(例如入口点或 ON 单元)都至少调用一次,以确保全部的 语句都至少执行一次。而对于包含多重条件判断的程序,最简单的测试准则是设计 出足够数量的测试用例,将每个判断的所有可能的条件结果的组合,以及所有的入 口点都至少执行一次(加入“可能”二字,是因为有些组合情况难以生成)。
4.1.2 等价划分(Equivalence Partitioning)
本书第 2 章将一个好的测试用例描述为具有相当高的可能性发现某个错误来, 此外还讨论了对程序的穷举输入测试是无法实现的。因此,当测试某个程序时,我 们就被限制在从所有可能的输入中努力找出某个小的子集。理所当然,我们要找的 子集必须是正确的,并且是可能发现最多错误的子集。
确定这个子集的一种方法,就是要意识到一个精心挑选的测试用例还应具备另 外两个特性:
1. 严格控制测试用例的增加,减少为达到“合理测试”的某些既定日标而必 须设计的其他测试用例的数量。
2. 它覆盖了大部分其他可能的测试用例。也就是说,它会告诉我们,使用或 不使用这个特定的输入集合,哪些错误会被发现,哪些会被遗漏掉。
虽然这两个特性看起来很相似,但描述的却是截然不同的两种思想。第一个特
性意味着,每个测试用例都必须体现尽可能多的不同的输入情况,以使最大限度地
减少测试所需的全部用例的数量。而第二个特性意味着应该尽量将程序输入范围进 行划分,将其划分为有限数量的等价类,这样就可以合理地假设(但是,显然不能 绝对肯定)测试每个等价类的代表性数据等同于测试该类的其他任何数据。也就是 说,如果等价类的某个测试用例发现了某个错误,该等价类的其他用例也应该能发 现同样的错误。相反,如果测试用例没能发现错误,那么我们可以预计,该等价类 中的其他测试用例不会出现在其他等价类中,因为等价类是相互交迭的。
这两种思想形成了称为等价划分的黑盒测试方法。第二种思想可以用来设计一 个“令人感兴趣的”输入条件集合以供测试,而第一个思想可以随后用来设计涵盖 这些状态的一个最小测试用例集。
本书第 l 章中三角形程序的一个等价类的例子是集合“三个值相等、都大于 0 的整型数据”。将此作为一个等价类后,我们就可以说,如果对该集合中某个元素 所进行的测试没有发现错误的话,那么对该集合中其他元素所进行的测试也不大可 能会发现错误。换言之,我们的测试时间最好花在其他地方(其他的等价类)。
使用等价划分方法设计测试用例主要有两个步骤:(1)确定等价类;(2)生成 测试用例。
外部条件
有效等价类
无效等价类
图 4-3 等价类列举表
1.确定等价类
确定等价类是选取每一个输入条件(通常是规格说明中的一个句子或短语)并 将其划分为两个或更多的组。可以使用图 4-3 中的表格来进行划分。注意,我们确 定了两类等价类:有效等价类代表对程序的有效输入,而无效等价类代表的则是其 他任何可能的输入条件(即不正确的输入值)。这样,我们遵循了本书第 2 章阐述
的测试原则,即要注意无效和未预料到的输入情况。
在给定了输入或外部条件之后,确定等价类大体上是一个启发式的过程。下面 给出了一些指导原则:
1. 如果输入条件规定了一个取值范围(例如,“数量可以是从1到 999”), 那么就应确定出一个有效等价类(1<数量<999 ) ,以及两个无效等价类(数 量<1,数量>999)。
2. 如果输入条件规定了取值的个数(例如,“汽车可登记一至六名车主”), 那么就应确定出一个有效等价类和两个无效等价类(没有车主,或车主多 于六个)。
3. 如果输入条件规定了一个输入值的集合,而且有理由认为程序会对每个值 进行不同处理(例如,“交通工具的类型必须是公共汽车、卡车、出租车、 火车或摩托车”),那么就应为每个输入值确定一个有效等价类和一个无效 等价类(例如,“拖车”)。
4. 如果存在输入条件规定了“必须是”的情况,例如“标识符的第一个字符 必须是字母”,那么就应确定一个有效等价类(首字符是字母)和一个无效 等价类(首字符不是字母)。
如果有任何理由可以认为程序并未等同地处理等价类中的元素,那么应该将这 个等价类再划分为小一些的等价类,稍后我们将给出这个过程的例子。
2.生成测试用例 第二步是使用等价类来生成测试用例,其过程如下:
1. 为每个等价类设置一个不同的编号。
2. 编写新的测试用例,尽可能多地覆盖那些尚未被涵盖的有效等价类,直到 所有的有效等价类都被测试用例所覆盖(包含进去)。
3. 编写新的用例,覆盖一个且仅一个尚未被覆盖的无效等价类,直到所有的 无效等价类都被测试用例所覆盖。
用单个测试用例覆盖无效等价类,是因为某些特定的输入错误检查可能会屏蔽 或取代其他输入错误检查。举例来说,如果规格说明规定了“请输入书籍类型(硬 皮、软皮或活页)及数量(l~999 )”,代表两个错误输入(书籍类型错误,数量
错误)的测试用例“XYZ 0”,很可能不会执行对数量的检查,因为程序也许会提示
“XYZ 是未知的书籍类型”,就不检查输入的其余部分了。
4.1.3 一个范例
作为一个例子,假设我们正在为 FORTRAN 语言的一个子集开发编译器,我们希 望对 DIMENSION 语句的语法检查进行测试。该语句的规格说明如下所示(这不是 FORTRAN 语言中的完整 DIMENSION 语句,我们对其进行了适当的剪裁,使其适合 作为教科书的样例。不要被其误导,以为测试实际的程序就像测试本书中的样例一 样容易)。在规格说明中,斜体字中的项是在实际语句中必须被特定实体取代的语 法单元,使用括弧代表可选项,省略号代表前面的项可能会连续重复出现多次。
DIMENSION 语句用来定义数组的大小 DIMENSION 语句的格式如下: DIMENSION ad[,ad]…
其中 ad 是数组描述符,其格式如下:
n(d[ ,d] …)
其中 n 是数组的符号名,d 是数组的维说明符。符号名可以由 1~6 个字母或数 字组成,其中首字符必须是字母。一个数组最少有 1 个维,最多有 7 个维。维 说明符的格式如下:
[lb: ]ub
其中 lb 与 ub 分别是维的下边界和上边界。边界可以是-65534~65535 之间 的一个常数,或是一个整型变变量名(但不能走数组元素名)。如果未指定 lb, 则其默认值为 1。ub 的值必须大于或等于 lb 。如果指定了 lb,则其值可为 负数、零或正数。就全部语句而言,DIMENSION 语句可写成连续多行(规格 说明结束)。
第一步应该是确定输入条件,然后为输入条件确定等价类。这些步骤都以表格 形式记录在表 4-l 中。括号中的数字代表不同等价类的标识符。
表 4-1 等价类
Input Condition
Valid Equivalence
Classes
Invalid Equivalence
Classes
Number of array descriptors
one (1), > one (2)
none (3)
Size of array name
1–6 (4)
0 (5), > 6 (6)
Array name
has letters (7), has digits
(8)
has something else (9)
Array name starts with letter
yes (10)
no (11)
Number of dimensions
1–7 (12)
0 (13), > 7 (14)
Upper bound is
constant (15), integer
variable (16)
array element name
(17),something else (18)
Integer variable name
has letter (19), digits (20)
has has something else
(21)
Integer variable starts with
letter
yes (22)
no (23)
Constant
-65534–65535 (24)
≤ 65534 (25), >65535
(26)
Lower bound specified
yes (27),
no (28)
Upper bound to lower bound
greater than (29), equal
(30)
less than (31)
Specified lower bound
negative (32), zero (33), >
0 (34)
Lower bound is
constant (35), integer
variable (36),
array element name (37)
something else (38)
Multiple lines
yes (39),
no (40)
下一个步骤应该是编写一个测试用例以覆盖一个或多个有效等价类。举例来说,
测试用例
DIMENSION A(2)
覆盖了第 1,4,7,10,12,15,24,28,29,40 等价类。 再下一个步骤应该是设计一个或更多的测试用例以覆盖剩余的有效等价类,如
以下形式的测试用例
DIMENSION A 12345 (1,9,J4XXXX,65535,1,KLM,
X 100),BBB[-65534:100,0:1000,10:10,I:65535]
覆盖了剩余的等价类。而无效输入等价类及其测试用例如下所示:
(3):DIMENSION (5):DIMENSION (10) (6):DIMENSION A234567(2) (9):DIMENSION A.1(2) (11):DIMENSION 1A(10) (13):DIMENSION B
(14):DIMENSION B(4,4,4,4,4,4,4,4) (17):DIMENSION B(4, A(2))
(18):DIMENSION B(4, 7)
(21):DIMENSION C(1, 10)
(23):DIMENSION C(10, 1J)
(25):DIMENSION C(-65535:1)
(26):DIMENSION C(65536)
(31):DIMENSION D(4:3)
(37):DIMENSION D(A(2):4)
(38):D(.:4)
因此,所有的等价类都被 18 个测试用例全部所覆盖了。读者可以考虑一下,如 何将这些测试用例与用特殊方法生成的测试用例集进行比较。
尽管等价划分方法要比随机选取测试用例优越得多,但它仍然存在不足。例如, 这种方法忽略了某些特定类型的高效测试用例,下面介绍的两种方法(边界值分析 与因果图)可以弥补其中的很多不足。
4.1.4 边界值分析(Boundary-Value Analysis)
经验证明,考虑了边界条件的测试用例与其他没有考虑边界条件的测试用例相 比,具有更高的测试回报率。所谓边界条件,是指输入和输出等价类中那些恰好处 于边界、或超过边界、或在边界以下的状态。边界值分析方法与等价划分方法存在 两方面的不同:
1. 与从等价类中挑选出任意一个元素作为代表不同,边界值分析需要选择一 个或多个元素,以便等价类的每个边界都经过一次测试。
2. 与仅仅关注输入条件(输入空间)不同,还需要考虑从结果空间(输出等 价类)设计测试用例。
很难提供一份如何进行边界值分析的“详细说明’,因为这种方法需要一定程 度的创造性,以及对问题采取一定程度的特殊处理办法(因此,就像测试的许多其 他方面一样,这更多的是项智力工作,并非其他的什么)。然而,我们还是给读者 提供一些通用指南:
1. 如果输入条件规定了一个输入值范围,那么应针对范围的边界设计测试用例, 针对刚刚越界的情况设计无效输入测试用例。举例来说,如果输入值的有效范 围是-1.0 至+l.0,那么应针对-1.0、1.0、-1.001 和 1.001 的情况设计测试用 例。
2. 如果输入条件规定了输入值的数量,那么应针对最小数量输入值、最大数量输 入值,以及比最小数量少一个、比最大数量多一个的情况设计测试用例。举例 来说,如果某个输入文件可容纳 l~255 条记录,那么应根据 0、l、255 和 256 条记录的情况设计测试用例。
3. 对每个输出条件应用指南 1。举例来说,如果某个程序按月计算FICA1的扣除额,
且最小金额是$0.00,最大金额为$1165.25,那么应该设计测试用例来测试扣除
$0.00 和$1165.25 的情况。此外,还应观察是否可能设计出导致扣除金额为负 数或超过$1165.25 的测试用例。注意,检查结果空间的边界很重要,因为输入 范围的边界并不总是能代表输出范围的边界情况(例如,三角正弦函数sin的情 况就如此)。同样,总是产生超过输出范围的结果也是不大可能的,但无论如何, 应该考虑这种可能性。
4. 对每个输出条件应用指南 2。如果某个信息检索系统根据输入请求显示关联程度 最高的信息摘要,而摘要的数量从未超过 4 条,则应编写测试用例,使程序显 示 0 条、l 条和 4 条摘要,还应设计测试用例,导致程序错误地显示 5 条摘要。
5. 如果程序的输入或输出是一个有序序列(例如顺序的文件、线性列表或表格), 则应特别注意该序列的第一个和最后一个元素。
6. 此外,发挥聪明才智找出其他的边界条件。
本书第 1 章中的三角形分析程序可以说明边界值分析的必要性。作为代表三角 形的输入值,它们必须是大于 0 的整数,而且其中任意两个之和应大于第三个。如 果定义了等价划分,可能会确定一个满足此条件的等价类,以及另一个两个输入之 和不大于第三个的等价类。因此,3-4-5 和 1-2-4 两个都是可能的测试用例。然而, 我们遗漏了一个可能的错误,即如果程序中表达式写成了 A+ B>=C,而不是 A+ B>C, 那么程序就会错误地告诉我们 l-2-3 表示的是一个有效的不规则三角形。因此,边 界值分析方法和等价划分之间的重要区别是,边界值分析考察正处于等价划分边界 或在边界附近的状态。
作为边界值分析的一个例子,考虑下面的程序规格说明:
MTEST 是一个多项选择考试的评分程序。程序的输入是一个名为 OCR 的数 据文件,包含多个长度为 80 个字符的记录。按照文件的格式要求.第一个记录 的内容是标题,作为每份输出报告的标题。后面的一组记录描述了试题的标准 答案,这些记录的最后一个字符是“2”。在这组记录的首条记录中,第 l~第 3 列存储的是试题的数量(一个 l~999 的数),第 10~第 59 存储的是第 l~第
1美国联邦社会保险捐款法.纳税人应依据此项法律交纳一定金额。-译者注
50 道试题的标准答案(任何字符都为有效答案),后续记录的第 10~第 59 列存
储的是第 51~第 100 道试题、第 101~第 150 道试题的标准答案等等。 第三组记录描述的是每个学生的答案,这些记录的最后一个字符皆为“3”。
对于每个学生来说,第一条记录的第 1~第 9 列存储的是学生的名字或编号(任
意字符),第 10~第 59 列存储的是该学生对第 l~第 50 道试题的答案。如果本 次考试试题超过 50 个,该学生的后续记录的第 10~第 59 列存储的是第 51~第
100、第 101~第 150 道试题的答案等等。学生的人数最多是 200。输入数据如 图 4-4 所示。四个输出报告分别是:
1.按学生的编号排序的报告,显示每名学生的成绩(正确答案的百分比)和名 次。
2.按成绩排序的报告。
3.显示成绩的平均值、中间值和标准偏差的报告。
4.按问题的编号排序的报告,显示正确回答每个问题的学生比例。
(规格说明结束)
标题
1 80
试题 数量
第1~50试题的答案
2
1 34 9 10 59 60 79 80
第51~100试题的答案
2
1 9 10 59 60 79 80
学生标识符
第1~50试题的答案
3
1 9 10 59 60 79 80
第51~100试题的答案
3
1 9 10 59 60 79 80
学生标识符
第1~50试题的答案
3
1 9 10 59 60 79 80
图 4-4 MTEST 程序的输入
我们从仔细阅读规格说明开始,寻找输入条件。第一个边界输入条件是一个空 输入文件。第二个输入条件是该标题记录,边界条件是标题记录不存在、可能的最 短标题和最长标题。后面的输入条件是存储标准答案的记录,以及第一个标准答案 记录里的“试题数量”域是否存在。试题数量的等价类不应是 l~999,因为在每个
50 的倍数处会出现某些特殊情况(例如,需要多个记录)。这种输入等价类的一个 合理划分是 1~50、51~999。因此,我们需要针对试题数量为 0、l、50、51 和 999 的情况设计测试用例。这样就覆盖了标准答案记录数量的大多数边界条件。然而, 三个最令人感兴趣的输入条件是标准答案记录不存在、记录多了一个以及记录少了 一个(例如,试题数量是 60 个,然而在某个情况下有三个标准答案记录,而在另 一种情况下只有一个)。到目前为止,我们生成的测试用例有:
1.输入文件为空。
2.没有标题记录。
3.标题只有 1 个字符。
4.标题有 80 个字符。
5.考试试题数量为 1。
6.考试试题数量为 50。
7.考试试题数量为 51。
8.考试试题数量为 999。
9.考试试题数量为 0。
10.试题数量域的值为非数字类型。
11.标题记录后无标准答案记录。
12.标准答案记录数量多一个。
13.标准答案记录数量少一个。 下面的输入条件是有关学生的答案的,其边界值测试用例可以是:
14.学生人数为 0 。
15.学生人数为 1。
16.学生人数为 200。
17.学生人数为 201。
18.某个学生只有一条答案记录,但却存在两条标准答案记录。
19.上面那个学生是文件中第一个学生。
20.上面那个学生是文件中的最后一个学生。
21.某个学生有两条答案记录,但只有一条标准答案记录。
22.上面那个学生是文件中第一个学生。
23.上面那个学生是文件中最后一个学生。 尽管有些输出边界(例如第一份输出报告为空)已被已有的测试用例覆盖到,
但我们仍然可以通过检查输出边界而得到有用的测试用例集。第一份输出报告与第
二份输出报告的边界条件是:
学生人数为 0(同第 14 号测试样例)。 学生人数为 1(同第 15 号测试样例)。 学生人数为 200(同第 16 号测试样例)。
24.所有学生的成绩相同。
25.所有学生的成绩都不相同。
26.部分、但不是全部学生的成绩相间(检查名次的计算是否正确)。
27.某个学生的成绩为 0 。
28.某个学生的成绩为 100 。
29.某个学生的标识符值为可能的最低值(检查排序)。
30.某个学生的标识符值为可能的最高值。
31.学生的数量恰好够一份报告占满一页(检查是否打印出多余页)。
32.学生的数量除够一份报告占满一页外,还多一个。 第三份输出报告(平均值、中间值和标准偏差)的边界条件是;
33.平均值为其最大值(全部学生都得满分)。
34.平均值为 0(全部学生都得 0 分)。
35.标准偏差为其最大值(一个学生成绩为 0 分,其他都为 100 分)。
36.标准偏差为 0(全部学生成绩相同)。
第 33 和第 34 号测试用例同时也覆盖了中间值的边界条件。另外一个有用的测 试用例是学生人数为 0 的情况(检查程序在计算平均值时是否有被 0 除的情况), 只是这种情况与第 14 号测试用例相同。
对第四份输出报告的检查可以生成下列边界值测试用例:
37.全部学生都回答正确第一道试题。
38.全部学生都回答错误第一道试题。
39.全部学生都回答正确最后一道试题。
40.全部学生都回答错误最后一道试题。
41.试题的数量恰好够一份报告占满一页.
42.试题的数量除够一份报告占满一页外,还多一道。 有经验的程序员很可能会认同这一点,即 42 个测试用例的大部分代表了在开发
该程序的过程中可能会犯的共性错误,但如果我们采用的是随机生成或特殊的测试 用例设计方法,这些错误中的大多数都不会被检查出来。如果使用得正确,边界值 分析是最为有效的测试用例设计方法之一。然而,这种方法常常使用得不好,因为 表面上它听起来比较简单。我们应该认识到,边界条件可能非常微妙,因此把它们 确定下来需要煞费一番脑筋。
4.1.5 因果图(Cause-Effect Graphing)
边界值分析和等价划分的一个弱点是未对输入条件的组合进行分析。举例来 说,上一节中介绍的 MTEST 程序由于试题数量与学生数量的乘积超过某个阈值时可 能会发生失效(例如,程序耗尽了内存)。边界值测试不一定能检查出此类错误。
对输入组合进
展开阅读全文