软件质量工程 解释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
    }
}
  1. 计算圈复杂度 (CC): 这个方法里有 1 个决策点 (if),所以圈复杂度是 1 + 1 = 2。 这意味着,理论上我们只需要 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 (“拒绝进入”)。

分析结果: 执行完这两个测试用例后,代码段 A 和代码段 B 都被覆盖了。所以,您的想法是对的,我们确实覆盖了所有的代码段

问题的关键:被遗漏的逻辑组合

现在,我们来看看这两个测试用例遗漏了什么:

if语句的条件是 age >= 18 && hasTicket。要使整个条件为false,有以下三种情况:

  1. age 不满足,hasTicket 满足 (false && true)
  2. age 满足,hasTicket 不满足 (true && false)
  3. 两者都不满足 (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)

总而言之,圈复杂度是一个非常有用的度量,它为测试设计提供了一个最低标准或一个很好的起点,但它绝不是测试工作的终点。专业的测试还需要考虑更多的逻辑组合和边界情况。

0%