0%

正确理解测试覆盖率数据

代码覆盖率(也被成为测试覆盖率)统计了源代码的那些行被测试执行过了。关于测试覆盖数据的一个常见误解是:

我的源代码的代码覆盖率很高;因此,我的代码是被充分测试了的。
上面的陈述试错误的!覆盖率高是个必要条件,但不是充分条件。
充分测试的代码 ===> 高覆盖率
充分测试的代码 <=X= 高覆盖率

最常用的覆盖率数据是行覆盖率(statement coverage)。这是收集成本最低的指标,也是最符合直觉的。行覆盖测量了一行代码是否被测试执行过。行覆盖没有测量程序执行路径被覆盖的比例。

行覆盖率的限制

  • TA不考虑所有可能的数据输入。考虑下面这段代码:

    1
    int a = b / c;

    这段代码可以被b = 18, a = 6覆盖,但没有被c = 0的场景测试过。

  • 一些工具没有提供fractional coverage。例如,在下面的代码中,当条件a为真的时候,代码就已经被100%覆盖了。条件b却没被验证过。

    1
    2
    3
    if (a || b) {
    // do something
    }
  • 测试覆盖分析只能告诉你已存在的代码被覆盖的程度。TA不能告诉你应该存在的代码是否被执行过。考虑下面的代码:
    1
    2
    3
    4
    5
    6
    7
    error_code = FunctionCall();
    // return kFatalError, kRecovableError, or kSuccess
    if (error_code == kFatalError) {
    // handle fatal error, exit
    } else {
    // assume call succeeded
    }
    这段代码只处理了3个可能返回值中的两个(是个Bug)。缺少了当返回kRecoverableError时的错误回复逻辑。对于哪些只产生kFatalError和kSuccess的测试用例来说,覆盖率就已经达到了100%。对于kRecoverableError的测试用例没有增加测试覆盖率,如果只从覆盖率目标来看像是“多余”的,但是TA能暴露出来一个Bug!

因此做覆盖率分析的正确姿势是:

  1. 确定你的测试用例足够可读,先不管测试覆盖率。
    这意味着写必要的测试用例,而不只是满足测试覆盖率要求的测试用例最小集
  2. 检查测试的覆盖率结果。找到被测试遗漏的代码。也要注意预期之外的覆盖模式,通常意味着存在Bug。
  3. 增加测试用例来覆盖2中发现的遗漏。
  4. 重复执行2-3 直到再增加测试用例就效用降低为止。如果有些边界场景难以测试,考虑把代码重构以提高可测试性。

参考这篇文章: 如何错误使用测试覆盖率

参考文献

https://testing.googleblog.com/2008/03/tott-understanding-your-coverage-data.html