Lecture 7. Structural Testing - Statement Coverage 结构测试 - 语句覆盖
现在开始白盒测试了~
白盒测试大概就是,研究程序的结构,从代码的角度分析有没有问题(听起来有点像静态查错?)
一般情况,白盒测试在黑盒测试之后进行。可以视为黑盒测试的增强、补充。
语句覆盖
就是看看代码的每一行是不是都能被执行到。步骤:
- 构造一个控制流图(control flow graph,CFG)~~上下文无关文法~~,大概就是流程图的意思。
- 每一个节点,都是一段不可分割的源代码语句。不可分割是指,这一段代码的第一行一旦被执行,后面的每一句都会执行。且对于第 $i+1$ 行,必须满足仅能从第 $i$ 行到达。
- 设计输入数据,让程序跑到每一个节点里面去
- 使用规格说明书(specification)判断 std output 应该是什么
控制流图 CFG
就是通过节点和边,把程序的功能流程表达出来。
- 有向图。
- 每一个节点的意义如上所述,在节点旁边标注一下是第几行到第几行
- 每一条边代表一个分支,通常 if 为真放左边,if 假的分支在右边
- 事实上,每条边也可以起一个名字,~~虽然不知道这个名字有啥用(~~
- 最精简情况下,每个节点的 入度或出度 肯定是大于 1 的(不是大于等于)。也就是说如果画完图发现存在一个节点,
max(ind, outd) = 1
,且这个节点不是根节点也不是叶子节点,那么这个节点就可以跟旁边的合并。具体走哪个分支,看逻辑条件选择 - 画完 CFG,验证一下,是不是所有行号都被包含进去了
- 不管用什么编程语言,都可以用控制流图描述
- 相当于一个简化的源代码模型
if () {} else {}
对于 if-else
代码块,算清楚哪里走到哪里就好了
switch ()
对于 switch
语句块,一堆相同反应的 case 可以当作同一行,放进同一个节点里,以达到精简的目的,如图:
switch
和 if-else
结合起来,会变得有点复杂,细心一点。
while ()
if ()
的第一行可以跟前面合并起来共享一个节点,但是由于 while ()
的第一行可能有不止一个入度,所以要跟前面拆开单独用一个节点。
for ()
for(a; b; c) {
这一行,在 CFG 当中,其实相当于的三行代码。所以画出来应该拆开,拆成 2a, 2b, 2c。
do {} while ();
其实跟 while
的差不多。左边的是 while()
,右边的是 do {} while ();
,就是条件判定语句先执行还是后执行得到区别。
判定和条件 decision and condition
判定是指 if(xxx)
条件是指 xxx
里面的布尔表达式
测试数据设计
本来,每一行代码(一个语句),都需要一个测试数据。但是可以用 CFG 把一些语句合并到一起,于是一个节点需要被一个测试数据覆盖。
找到 $1$ 到 $n$ 节点的所有路径,每个路径对应一个测试数据。
可以画个图,用橙色表示第一次覆盖的节点,白色表示之前被覆盖过的节点。表格里面,依然用中括号表示已经覆盖过的节点。
注意,期望的样例输出,应该根据规格说明书手算,不能看着代码来算,要不然无论怎么测都是对的。
jUnit
eclipse 里面,装上一个插件, 就会有一个按钮,可以用红黄绿表示,每一行代码有没有被执行,以及每个 if
的 $2 \times n$ 个单独的分支条件(不是 $2^n$ 种组合情况),是部分执行还是完全执行
甚至可以统计代码覆盖率是 % 多少,追求 100% 覆盖。
但是那个插件好像只能在 eclipse 里面用,所以 vscode 里面能不能实现类似的功能就不清楚了~
步骤总结
- 画出 CFG 控制流图
- 跟着 CFG 走一走
- 根据规格说明书判断预期输出,然后执行输入数据的测试
缺点
- 由于希望用尽量少的路径覆盖所有节点,所以在没有
else
的if
那里,if
条件不满足的情况是测不到的(CFG 是一个三角) - 由于语言的短路逻辑特性,可能一个条件语句只被执行了一半,就直接短路了
- 有时候,输入数据不太容易设计
- 不能覆盖所有的逻辑值的情况