软件质量工程 解释1
根据圈复杂度设计的测试用例不应该包含全部的代码段吗?
简单来说:是的,根据圈复杂度设计的测试用例通常可以覆盖全部的代码段(语句覆盖),但它不能覆盖所有可能的执行路径组合和逻辑条件。
让我们用一个例子来详细解释。
什么是圈复杂度(Cyclomatic Complexity)测试?
圈复杂度(CC)的值等于代码中“线性独立路径”的数量。目标是设计出数量等于 CC 值的测试用例,当这些用例全部执行后,可以确保代码中的每一行语句至少被执行过一次。这被称为语句覆盖(Statement Coverage)或分支覆盖(Branch Coverage)。
例子:为什么语句覆盖不等于测试充分
假设我们有以下一段简单的 Java 代码,用于检查是否允许进入某个场所:
public void checkAccess(int age, boolean hasTicket) {
// 决策点:一个复杂的 if 条件
if (age >= 18 && hasTicket) {
System.out.println("允许进入 (Access Granted)"); // 代码段 A
} else {
System.out.println("拒绝进入 (Access Denied)"); // 代码段 B
}
}
-
计算圈复杂度 (CC): 这个方法里有 1 个决策点 (
if
),所以圈复杂度是1 + 1 = 2
。 这意味着,理论上我们只需要 2 个 测试用例就能实现分支覆盖。 -
设计测试用例 (数量 = CC):
- 测试用例 1:
age = 20
,hasTicket = true
- 条件判断:
20 >= 18 && true
->true && true
->true
- 结果: 执行代码段 A (“允许进入”)。
- 条件判断:
- 测试用例 2:
age = 16
,hasTicket = false
- 条件判断:
16 >= 18 && false
->false && false
->false
- 结果: 执行代码段 B (“拒绝进入”)。
- 条件判断:
- 测试用例 1:
分析结果: 执行完这两个测试用例后,代码段 A 和代码段 B 都被覆盖了。所以,您的想法是对的,我们确实覆盖了所有的代码段。
问题的关键:被遗漏的逻辑组合
现在,我们来看看这两个测试用例遗漏了什么:
if
语句的条件是 age >= 18 && hasTicket
。要使整个条件为false
,有以下三种情况:
age
不满足,hasTicket
满足 (false && true
)age
满足,hasTicket
不满足 (true && false
)- 两者都不满足 (
false && false
)
我们的测试用例 2只覆盖了上面第 3 种情况 (false && false
)。我们完全没有测试过另外两种情况:
- 被遗漏的场景 1:
age = 15
,hasTicket = true
(年龄不够但有票) - 被遗漏的场景 2:
age = 25
,hasTicket = false
(年龄足够但没票)
如果代码的 Bug 恰好隐藏在这两种被遗漏的场景中(比如,系统本应在“年龄足够但没票”时提示“请购票”,而不是简单地“拒绝进入”),我们的测试就无法发现这个 Bug。
结论
因此,最初那句话是完全正确的:
-
“Achieving a number of test cases equal to the cyclomatic complexity ensures all control statements are executed”
- 是的,达到圈复杂度的测试用例数可以确保每个代码段(语句)都被执行到,实现“语句覆盖”或“分支覆盖”。
-
“but it does not guarantee testing sufficiency, as it doesn’t cover all possible combinations of paths or data dependencies”
- 但是,它不保证测试是充分的。因为它不关心决策点内部的逻辑组合(如上例中的
&&
条件),也无法覆盖更复杂的数据依赖关系。要覆盖所有逻辑组合,你需要更强的覆盖标准,如条件组合覆盖(Multiple Condition Coverage)。
- 但是,它不保证测试是充分的。因为它不关心决策点内部的逻辑组合(如上例中的
总而言之,圈复杂度是一个非常有用的度量,它为测试设计提供了一个最低标准或一个很好的起点,但它绝不是测试工作的终点。专业的测试还需要考虑更多的逻辑组合和边界情况。