2.2 覆盖分析法
测试工作从代码行分析开始
拿到一段代码后,该该如何着手设计测试用例呢?我们知道,任何被测代码或被测函数都是由代码行所组成的,那么很自然的,考虑从覆盖所有代码行着手进行测试用例设计。
示例代码
public int example(int x, int y, int z) {
int result = 0;
if (x >= 3 && y == 0) {
result = z + x;
}
if (z <= 5 || x < z) {
result = result + x;
} else {
result = result + y;
}
return result;
}一、语句覆盖(Statement Coverage)
定义
设计了足够多的测试用例,使得程序执行了所有的语句的测试用例设计技术。
特点
- 确保程序中的每条可执行语句至少被执行一次
- 是逻辑覆盖法中一种方法
- 所谓逻辑覆盖法就是通过对程序逻辑结构的遍历实现程序覆盖的一种白盒测试技术
测试用例
| 序号 | 覆盖语句 | 测试用例 | 期望结果 |
|---|---|---|---|
| 1 | c, d | x=4, y=0, z=6 | 返回14 |
| 2 | c, e | x=8, y=0, z=6 | 返回14 |
局限性
虽然语句覆盖遍历了所有的语句,但是如果程序员在判定逻辑运算出错,例如把”x>=3&&y==0”错写成”x>=3||y==0”后,测试仍然通过!可见对于判定逻辑运算符的错误,语句覆盖可能会遗漏。
二、判定覆盖(Decision Coverage)
定义
通过执行足够的测试用例,使得程序中的每个判定中的判定表达式的值为”真”和为”假”的分支都至少要执行一次。
别名
也称为分支覆盖
测试用例
| 序号 | 覆盖路径 | 测试用例 | 期望 | 实际 | 测试结论 |
|---|---|---|---|---|---|
| 1 | abd | x=7, y=8, z=6 | 8 | 21 | 失败 |
| 2 | ace | x=3, y=0, z=2 | 8 | 8 | 通过 |
局限性
如果错误出现在条件表达式中,缺陷可能无法被发现。例如将”x>=3”错写成”x⇐3”,测试仍然通过。
三、条件覆盖(Condition Coverage)
定义
设计足够多的测试用例,使得程序中每个判定表达式包含的每个条件的可能取值(真/假)都至少满足一次。
别名
也称为谓词覆盖(Predicate Coverage)
条件标记
对第一个判定条件(x≥3&&y==0)分解:
- 条件x≥3:取真值记为T₁,取假值记为F₁
- 条件y==0:取真值记为T₂,取假值记为F₂
对第二个判断条件(z≤5||x<z)分解:
- 条件z≤5:取真值记为T₃,取假值记为F₃
- 条件x<z:取真值记为T₄,取假值记为F₄
测试用例
| 序号 | 覆盖条件 | 测试用例 | 覆盖分支 | 期望 |
|---|---|---|---|---|
| 1 | T₁, F₂, T₃, F₄ | x=5, y=6, z=4 | be | 5 |
| 2 | F₁, T₂, F₃, T₄ | x=2, y=0, z=6 | be | 2 |
局限性
虽然覆盖了8个条件满足条件覆盖,但是只覆盖了be分支,c和d分支没有覆盖到,并不满足判定覆盖。
四、判定-条件覆盖(Decision-Condition Coverage)
定义
设计足够多的测试用例,使得程序中每个判定包含的每个条件的真和假都至少执行一次,并且每个判定本身的判定结果真和假也至少执行一次。
测试用例
| 序号 | 覆盖条件 | 测试用例 | 覆盖分支 | 期望 |
|---|---|---|---|---|
| 1 | T₁, T₂, F₃, F₄ | x=8, y=0, z=6 | cd | 14 |
| 2 | F₁, F₂, T₃, T₄ | x=2, y=8, z=4 | be | 2 |
局限性(短路问题)
某些情况下,当左边的条件表达式能决定结果,那么右边的表达式就被跳过不检查了短路操作符,这就是(Short Circuit Operators)。
例如:对于判定(z≤5||x<z),如果z≤5为真,就认为该判定为真,这时编译器将不会再检查x<z。
五、条件组合覆盖(Multiple Condition Coverage)
定义
通过执行足够多的测试用例,使得程序中每个判定的所有可能的条件取值组合都至少出现一次,并且每个判断本身的判定结果也至少出现一次。
别名
也称为多重条件覆盖
条件组合列表
第一个判定条件(x≥3&&y== 0)的组合:
| 组合号 | 条件组合 | 条件标记 | 判定取值 |
|---|---|---|---|
| 1 | x≥3, y== 0 | T₁, T₂ | 第1个为真 |
| 2 | x≥3, y!=0 | T₁, F₂ | 第1个为假 |
| 3 | x<3, y== 0 | F₁, T₂ | 第1个为假 |
| 4 | x<3, y!=0 | F₁, F₂ | 第1个为假 |
第二个判断条件(z≤5||x<z)的组合:
| 组合号 | 条件组合 | 条件标记 | 判定取值 |
|---|---|---|---|
| 5 | z≤5, x<z | T₃, T₄ | 第2个为真 |
| 6 | z≤5, x≥z | T₃, F₄ | 第2个为真 |
| 7 | z>5, x<z | F₃, T₄ | 第2个为真 |
| 8 | z>5, x≥z | F₃, F₄ | 第2个为假 |
组合覆盖测试用例
满足条件组合覆盖,则满足判定覆盖、条件覆盖和判定-条件覆盖。
单元测试步骤总结
完成单元代码的自动化测试只需3步:
第一步:设计测试用例
从分析程序结构着手,设计测试用例覆盖所有的代码行。
第二步:编写测试代码
使用JUnit单元测试框架编写测试代码。
public class CalculatorTest {
Calculator cal = new Calculator();
@Test
public void testExample_Statement_cd() {
int except = 14;
int actual = cal.example(4, 0, 6);
assertEquals(except, actual);
}
@Test
public void testExample_Statement_ce() {
int except = 14;
int actual = cal.example(8, 0, 6);
assertEquals(except, actual);
}
}第三步:执行测试
运行测试代码,如果JUnit的Runner进度条是绿色,则测试通过;如果是Failures,则是断言捕获的错误,很可能是程序的BUG。
回归测试
**回归测试(Regression Testing)**是指修改了旧代码后,重新进行测试以确认修改没有引入新的错误或导致其他代码产生错误。
以后只需要重复执行测试代码,就可以无限次的反复执行测试。例如,其他与example()相关方法有发生改动,只需重复执行该单元测试,就可以快速判断是否由于其他方法的修改引入了该方法新的错误。
覆盖方法总结
语句覆盖,代码中的执行语句(非判断语句)都至少执行一遍 判定覆盖,强调所有的判断语句的分支,真假都至少执行一遍 条件覆盖,强调判断语句当中的子条件都至少执行一遍,而非判断结果的分支 判定-条件覆盖:即满足判定覆盖,也满足条件覆盖的条件 条件组合覆盖:因为判定-条件覆盖会因为逻辑操作符而短路,导致问题,因此需要将判定-条件覆盖进行加强,具体表现为:将条件覆盖里面的所有子条件进行组合,并满足判定覆盖的条件 修正条件-判定组合覆盖