白盒测试是软件测试中的一种典型的场景。在这种场景下,测试人员能够获得程序代码,在对代码中的控制流、数据流等信息的基础上设计测试用例,并根据测试用例的实际运行结果来判断程序是否符合期望、是否存在 bug、测试用例的质量高低等。
人们常用代码覆盖率来衡量一个测试用例是否充分地测试了被测程序,一般而言,如果一个测试用例尽可能多地执行了代码,触发了尽可能丰富的事件,那么就说明测试越充分。如果没有出现明显的 bug,那么程序的质量就越高,面对实际情况下用户给定输入的表现就越稳定(鲁棒)。
一个程序的代码中通常包含循环、条件、函数调用等特殊结构,根据针对这些特殊结构设计不同的覆盖规则可以衍生出不同的代码覆盖标准,常见的有:
- 语句覆盖
- 分支覆盖(判定覆盖)
- 条件覆盖
- 路径覆盖
以如下的程序为例:
语句覆盖#
语句覆盖(statement coverage)仅仅从执行的代码的行数来度量覆盖率,覆盖率的分母为被测程序源文件代码的总行数,分子为程序运行过程中经过的代码行数。例如,示例程序总共有 10 行代码,其中第 1 行和第 10 行必定会被执行。当输入 a = 2, b = 3, c = 2
时,第 2、3、6、7 行会被执行到,所以语句覆盖率为 60%。因为第 4 行和第 8 行是互斥的,所以仅靠一个组输入是无法达到 100% 语句覆盖的,可以构造 2 组输入:a = 2, b = 3, c = 4
和 a = 2, b = 3, c = 1
,依靠这两组输入可以实现 100% 语句覆盖。
分支覆盖(判定覆盖)#
分支覆盖(branch coverage)也叫判定覆盖,要求每个带有条件判定的语句的各个分支都需要被覆盖到。如果将代码的逻辑表示成一张流程图的话,分支覆盖也可以直观地理解为图中的所有连线和箭头都要被覆盖到:
如果将必定会连续执行的代码合并成一个节点,那么这张图就可以稍作简化为:
虽说是要所有的连线和箭头都覆盖到,但一般只考虑条件语句和循环语句所产生的分支。例如对于第 2 行的条件语句,需要覆盖 2->3
和 2->6
这两个分支;对于第 4 行的条件语句,需要覆盖 4->5
和 4->6
这两个分支。
示例程序中总共有 4 个条件语句,可以取 4 个真值。对于一组输入 a = 2, b = 3, c = 2
,它运行后在各个条件语句产生的判定值为 T, F, T, F
,分别对应分支 2->3, 3->6, 6->7, 7->10
, 要让分支覆盖率达到 100%,只要创造一组输入,使得判定值取 F, T, F, T
即可,但是在示例程序中,第一个条件取 F 是无法让第二个条件取 T 的。下面这 3 组输入则可以让分支覆盖率达到 100%:
a = 2, b = 3, c = 2 => T, F, T, F
a = 10, b = 3, c = 4 => T, T, F, _
a = 1, b = 1, c = 1 => F, _, T, T
每个判定都取到了 T 和 F,满足了分支覆盖的要求。
条件覆盖#
一个判定往往会由多个条件(一个能够产生布尔值的最小的表达式)经过与、或、非等布尔运算组合而成,因此以更加细粒度的视角去衡量控制流的变化,可以用条件的取值取代上述分支覆盖中的判定概念,构成条件覆盖度量。在示例程序中,对于第 2 行代码,需要考虑的不再是 a > 1 and b > 2
取 T、F 的情况,而是 a > 1
和 b > 2
分别取 T、F 的情况。在条件覆盖中,只需要每个条件都取过 T 和 F 即可,例如一个输入产生 T、T,另一个输入产生 F、F,即达到了条件覆盖 100%。在分支覆盖中给出的 3 组输入:
a = 2, b = 3, c = 2 => T, F, T, F
a = 10, b = 3, c = 4 => T, T, F, _
a = 1, b = 1, c = 1 => F, _, T, T
缺少了 a < 1
的情况,因此只要把第三组输入替换为 a = 0, b = 1, c = 1
。
路径覆盖#
路径覆盖是最细粒度的覆盖,他将程序从入口(一般是 main 函数)到出口(exit、panic、main 函数中的 return 语句)的每一条语句组合成路径,只要有一条语句不同,就视为不同的路径。示例程序存在的路径为(希望我没有疏漏):
1,2->3,4->5->6->7,8->9->10
1,2->3,4->5->6->7,8->10
1,2->3,4->5->6->10
1,2->3,4->6->7,8->9->10
1,2->3,4->6->7,8->10
1,2->3,4->6->10
1,2->6->7,8->9->10
1,2->6->7,8->10
1,2->6->10
需要注意的是,这只是从程序代码中分析得到的所有可能的路径,实际情况下有些路径是不成立的,例如 3,4->5
和 7,8->9
不可能出现在同一条路径中,因此,对于示例程序,100% 路径覆盖是不可能的。
路径覆盖是这几种覆盖度量中要求最高的,只要满足了路径覆盖,就必然能够满足其他所有覆盖(实际不存在的情况除外)。但是在实际生活中的程序不仅包含数不清的条件语句,还有循环语句,以及多个条件和循环语句的组合,产生的路径数量会随着条件、循环语句的个数的增长呈现出指数级的上升,要实现较高的路径覆盖率是极为困难的。给定一个程序的代码,要计算程序中存在的路径数量也是几乎不可能的,因此需要计算路径覆盖时往往采用计数而不是计算比率的方式,能够出发更多不同路径的输入被视作更高质量的测试用例。