动态测试是指通过运行代码来观察代码运行状况,利用查看代码和实现方法得到的信息来确定 哪些需要测试、哪些不需要测试、如何开展测试,动态测试又称为结构化测试。常见的动态测试方 法有:语句覆盖、判定覆盖、条件覆盖、判定/条件覆盖、路径覆盖和基本路径覆盖。 以如图 10-8 所示的程序流程图为例,对动态测试技术进行分析。
语句覆盖 语句覆盖是指在测试过程中,设计若干个测试用例,然后运行被测试程序,保证程序中每条可 执行的语句至少被执行一次。若干个测试用例是指使用最小的测试用例数来覆盖所有的执行语句。 如图 10-8 所示的程序流程图,只要设计一个测试用例即可,执行的路径为 acdfg。 测试用例:iLoop=9,szT= "/*",bIs=T; 语句覆盖的优点如下: (1)能够检查所有语句。 (2)结构简单的代码的测试效果较好。
(3)容易实现自动测试。 (4)代码覆盖率比较高。 (5)如果是程序块覆盖,则不涉及程序块中的源代码。 上面的实例中看似每条语句都被执行了一次,但依然存在问题,语句覆盖无法测试到以下几个 方面的内容: (1)条件语句中逻辑运算符的正确性无法测试。 如实例中的第二个判断条件 szT== "/*"&&bIs==T,如果将该测试用例更改为 szT=="a"&&bIs==F, 那么同样会执行路径 acde,如果程序中错误地将逻辑条件“与”写成了“或”,是无法测试出错 误的。(2)循环次数错误、跳出循环条件错误。 如实例中的第一个判断条件程序错误地写成了 iLoop<=10,那么执行第一个测试用例还是无法 测试出错误。 (3)语句覆盖率高并不代表测试很全面。 如下面的代码,执行 x=2 的测试用例,测试结果语句的覆盖率达到 99%,但是程序中一个重 要的分支没有被测试到,存在严重的缺陷。
判定覆盖 判定覆盖是指设计若干测试用例,运行被测程序,使程序中每个判断的取真分支和取假分支至 少经历一次,即判断的真假值均被满足。判定覆盖又称为分支覆盖。 如图10-8所示的程序流程图,只要设计三个测试用例即可,执行的路径分别为ab、acde和acdfg。 测试用例 1:iLoop=11; 测试用例 2:iLoop=9,szT= "/*",bIs=F; 测试用例 3:iLoop=9,szT= "/*",bIs=T; 可以看出,这三个测试用例不仅满足判定覆盖还满足语句覆盖,所以其实判定覆盖是语句覆盖 的一种增强版形式。因此语句覆盖存在的缺点在判定覆盖中依然存在,具体的缺点如下:
(1)条件语句中逻辑运算符的正确性无法测试。 (2)循环次数错误无法测试。 (3)跳出循环条件错误无法测试。
条件覆盖 条件覆盖是指设计若干测试用例,执行被测程序以后,确保每个判断中每个条件的可能取值至 少满足一次。 首先将程序中每个分支中的条件取值情况列出来,如图 10-8 所示的程序流程图,各分支条件 取值和标记见表 10-4。
接着对条件进行组合,保证每个条件的取值至少被执行一次,再根据组合条件写出测试用例。 在本例中只要两个测试用例即可覆盖到每个条件的取值情况,测试用例见表 8-5。
那么条件覆盖一定会满足分支覆盖吗?仔细分析表 10-5 的测试用例可以发现,有一条分支并 没有被执行,即路径 acdfg。 所以,条件覆盖的优点是可以检查所有的条件错误;缺点是不能保证每个分支被覆盖,即不能 实现对每个分支的检查,同时相对于语句覆盖,测试用例的数量也会增多。所以为了在条件覆盖的 基础上达到分支覆盖,必须使用判定/条件覆盖分析法。
判定/条件覆盖 判定/条件覆盖是指设计若干个测试用例,然后运行被测程序,使得判断中每个条件的所有可 能至少出现一次,并且每个判断本身的判定结果也至少出现一次。
首先将程序中每个分支中的条件取值和分支组合的取值情况列出来,如图 10-8 所示的程序流 程图,各分支条件取值和分支组合取值见表 10-6。
接着对条件进行组合,保证每个条件的取值以及每个分支至少被执行一次,再根据组合条件写 出测试用例。在本例中只要两个测试用例即可覆盖到每个条件的取值情况,测试用例见表 10-7。
该实例中只要设计三个测试用例即可覆盖所有分支中的条件检查和分支检查,那么判定/条件 覆盖分析法能覆盖所有的路径吗?答案是肯定的,判定/条件覆盖分析法并不一定能覆盖所有路径 的检查,但该实例中恰好全部覆盖了所有的路径。 所以判定/条件覆盖分析法的优点是既考虑了每个条件的检查,又考虑了每个分支的检查,发 现错误的能力强于单独的分支覆盖和条件覆盖;缺点是判定/条件覆盖并不能全面覆盖所有路径, 并且相对前面几种分析方法,其测试用例数量也有所增加。
路径覆盖 前面讨论的多种覆盖准则,有的虽提到了所走路径问题,但尚未涉及到路径的覆盖。而路径 能否全面覆盖在软件测试中是一个重要问题,因为程序要取得正确的结果,就必须消除遇到的各 种障碍,沿着特定的路径顺利执行。只有程序中的每一条路径都得到测试,才能说程序受到了全 面检验。 路径覆盖是指设计足够多的测试用例,覆盖程序中所有可能的路径。 如图 10-8 所示的程序流程图,只要设计三个测试用例即可覆盖所有路径,测试用例见表 10-8。
路径覆盖看似可以完成覆盖程序中的所有路径,但对于一个复杂的循环语句,则很难全面覆盖 所有执行路径。如图 10-9 所示的循环程序,其包含的不同执行路径条数达 5 20,假定对每一条路径 进行测试需要 1 毫秒,一年工作 365×24 小时,要想把所有路径测试完需 3170 年。 所以在实际的测试过程中要做到完全的路径覆盖是无法实现的,为解决这一难题,只能把覆盖 的路径数压缩到一定限度内。
基本路径覆盖 基本路径覆盖法是指在程序控制流图的基础上,通过分析控制结构的环路复杂性,导出基 本可执行路径集合,设计测试用例的方法。该方法把覆盖的路径数压缩到一定限度内,程序中 的循环体最多只执行一次。设计出的测试用例要保证在测试中,程序的每一个可执行语句至少 执行一次。基本路径覆盖分析法步骤如下: (1)从详细设计导出控制流图。 符号“○”为控制流图的一个结点,表示一个或多个无分支的源程序语句。箭头为边,表示控 制流的方向。 常见的顺序结构、If 选择结构、While 重复选择结构、Until 重复选择结构和 Case 多分支选择 结构控制流图画法如图 10-10 所示。
在选择或多分支结构中,分支的汇聚处应有一个汇聚结点。边和结点圈定的区域叫做区域,当 对区域计数时,图形外的区域也应记为一个区域。如果判断中的条件表达式是由一个或多个逻辑运 算符(OR、AND、NAND、NOR)连接的复合条件表达式,则需要改为一系列只有单条件的嵌套 的判断。如图 10-11 所示为程序流程图与控制流图的关系
(2)确定控制流图的环形复杂度。 环形复杂度也称为圈复杂度,它是一种为程序逻辑复杂度提供定量尺度的软件度量。程序的环 形复杂度是指程序基本路径集中的独立路径数量,这是确保程序中每个可执行语句至少执行一次所 必需的测试用例数目的上限。独立路径是指程序中至少引入了一个新的处理语句集合或一个新条件 的程序通路。从控制流图的角度来说,独立路径是指必须至少包含一条在本次定义路径之前不曾用 过的边。 环形复杂度以图论为基础,为我们提供了非常有用的软件度量。可用如下三种方法来计算环形 复杂度: 第一种方法:计算控制流图中的区域数量,控制流图中的区域等于环形复杂度。 第二种方法:给定控制流图 G 的环形复杂度 V(G),定义 V(G) = E-N+2(其中 E 是控制流图中边的数量,N 是控制流图中的结点数量)。 第三种方法:给定控制流图 G 的环形复杂度 V(G),也可定义为 V(G) = P+1(其中 P 是控制流 图 G 中的结点,但该结点至少需要输出 2 条或 2 条以上的边)。 (3)确定独立路径的基本集。 (4)导出测试用例,确保基本路径集中的每一条路径都被执行。 【实例】使用基本路径覆盖法对下面一段程序进行分析,并导出测试用例,被测试代码如下:
第一步:分析被测试程序,画出程序流程图和控制流图,如图 10-12 所示。
第二步:确定控制流图的复杂度,该实例的复杂度为 9(复杂度=21-14+2)。 第三步:确定独立路径的基本集,该实例独立路径的基本集如下: 路径 1:开始→(1-8,9)→结束; 路径 2:开始→(1-8,9)→(10-11,12a)→(16a)→(20a)→(22)→(1-8,9)→结束;路径 3:开始→(1-8,9)→(10-11,12a)→(16a)→(20a)→(20b)→(22)→(1-8,9)→结束; 路径 4:开始→(1-8,9)→(10-11,12a)→(16a)→(20a)→(20b)→(20c)→(22)→(1-8,9)→结束; 路径 5:开始→(1-8,9)→(10-11,12a)→(16a)→(20a)→(20b)→(20c)→(21)→(22)→(1-8,9)→结束; 路径 6:开始→(1-8,9)→(10-11,12a)→(12b)→(13-15)→(1-8,9)→结束; 路径 7:开始→(1-8,9)→(10-11,12a)→(16a)→(16b)→(17-19)→(1-8,9)→结束; 路径 8:开始→(1-8,9)→(10-11,12a)→(12b)→(16a)→(20a)→(22)→(1-8,9)→结束; 路径 9:开始→(1-8,9)→(10-11,12a)→(16a)→(16b)→(20a)→(22)→(1-8,9)→结束。 第四步:依据独立路径的基本集导出测试用例。该实例中包括 9 个测试用例,此处不一一列出, 以路径 1 为例进行导出测试用例操作,导出的测试用例见表 10-9。