跳转至

Lecture 7. Structural Testing - Statement Coverage 结构测试 - 语句覆盖

现在开始白盒测试了~

白盒测试大概就是,研究程序的结构,从代码的角度分析有没有问题(听起来有点像静态查错?)

一般情况,白盒测试在黑盒测试之后进行。可以视为黑盒测试的增强、补充。

语句覆盖

就是看看代码的每一行是不是都能被执行到。步骤:

  1. 构造一个控制流图(control flow graph,CFG)~~上下文无关文法~~,大概就是流程图的意思。
  2. 每一个节点,都是一段不可分割的源代码语句。不可分割是指,这一段代码的第一行一旦被执行,后面的每一句都会执行。且对于第 $i+1$ 行,必须满足仅能从第 $i$ 行到达。
  3. 设计输入数据,让程序跑到每一个节点里面去
  4. 使用规格说明书(specification)判断 std output 应该是什么

控制流图 CFG

就是通过节点和边,把程序的功能流程表达出来。

  • 有向图。
  • 每一个节点的意义如上所述,在节点旁边标注一下是第几行到第几行
  • 每一条边代表一个分支,通常 if 为真放左边,if 假的分支在右边
  • 事实上,每条边也可以起一个名字,~~虽然不知道这个名字有啥用(~~
  • 最精简情况下,每个节点的 入度或出度 肯定是大于 1 的(不是大于等于)。也就是说如果画完图发现存在一个节点,max(ind, outd) = 1,且这个节点不是根节点也不是叶子节点,那么这个节点就可以跟旁边的合并。具体走哪个分支,看逻辑条件选择
  • 画完 CFG,验证一下,是不是所有行号都被包含进去了
  • 不管用什么编程语言,都可以用控制流图描述
  • 相当于一个简化的源代码模型

if () {} else {}

对于 if-else 代码块,算清楚哪里走到哪里就好了

image-20221016130255957

switch ()

对于 switch 语句块,一堆相同反应的 case 可以当作同一行,放进同一个节点里,以达到精简的目的,如图:

image-20221016130310776

switchif-else 结合起来,会变得有点复杂,细心一点。

image-20221016130349338

while ()

if () 的第一行可以跟前面合并起来共享一个节点,但是由于 while () 的第一行可能有不止一个入度,所以要跟前面拆开单独用一个节点。

image-20221016130429240

for ()

for(a; b; c) { 这一行,在 CFG 当中,其实相当于的三行代码。所以画出来应该拆开,拆成 2a, 2b, 2c。

image-20221016130440243

do {} while ();

其实跟 while 的差不多。左边的是 while(),右边的是 do {} while ();,就是条件判定语句先执行还是后执行得到区别。

image-20221016130659737

判定和条件 decision and condition

判定是指 if(xxx)

条件是指 xxx 里面的布尔表达式

测试数据设计

本来,每一行代码(一个语句),都需要一个测试数据。但是可以用 CFG 把一些语句合并到一起,于是一个节点需要被一个测试数据覆盖。

找到 $1$ 到 $n$ 节点的所有路径,每个路径对应一个测试数据。

可以画个图,用橙色表示第一次覆盖的节点,白色表示之前被覆盖过的节点。表格里面,依然用中括号表示已经覆盖过的节点。

image-20221016130222654

注意,期望的样例输出,应该根据规格说明书手算,不能看着代码来算,要不然无论怎么测都是对的。

jUnit

eclipse 里面,装上一个插件, 就会有一个按钮,可以用红黄绿表示,每一行代码有没有被执行,以及每个 if 的 $2 \times n$ 个单独的分支条件(不是 $2^n$ 种组合情况),是部分执行还是完全执行

甚至可以统计代码覆盖率是 % 多少,追求 100% 覆盖。

但是那个插件好像只能在 eclipse 里面用,所以 vscode 里面能不能实现类似的功能就不清楚了~

步骤总结

  1. 画出 CFG 控制流图
  2. 跟着 CFG 走一走
  3. 根据规格说明书判断预期输出,然后执行输入数据的测试

缺点

  1. 由于希望用尽量少的路径覆盖所有节点,所以在没有 elseif 那里,if 条件不满足的情况是测不到的(CFG 是一个三角)
  2. 由于语言的短路逻辑特性,可能一个条件语句只被执行了一半,就直接短路了
  3. 有时候,输入数据不太容易设计
  4. 不能覆盖所有的逻辑值的情况