32

我正在寻找代码覆盖率的一些不良副作用的真实示例。

我注意到最近在工作中发生了这种情况,因为有一项实现 100% 代码覆盖率的政策。代码质量肯定一直在提高,但相反,测试人员似乎正在编写更宽松的测试计划,因为“代码完全经过单元测试”。结果,一些逻辑错误设法溜走了。调试它们真的很痛苦,因为“代码已经过完全单元测试”。

我认为这部分是因为我们的工具只进行了语句覆盖。尽管如此,它本来可以更好地度过时间。

如果有人对代码覆盖政策有其他负面影响,请分享。我想知道现实世界中正在发生什么样的其他“问题”。

提前致谢。

编辑:感谢所有非常好的回应。有一些我会标记为答案,但不幸的是我只能标记一个。

4

13 回答 13

59

一句话:代码覆盖率告诉你你肯定没有测试过什么,而不是你有什么。

构建有价值的单元测试套件的一部分是找到最重要的、高风险的代码并提出难题。您要确保将艰难的事情作为优先事项。覆盖率数字没有代码的“重要性”概念,也没有测试质量的概念。

根据我的经验,您将编写的许多最重要的测试都是几乎不增加任何覆盖率的测试(边缘情况会在这里和那里增加一些额外的百分比,但会发现大量错误)。

设置硬性和(可能适得其反的)覆盖率目标的问题在于,开发人员可能不得不开始向后弯腰来测试他们的代码。让代码可测试,然后就是折磨。如果您通过出色的测试达到 100% 的覆盖率,那就太棒了,但在大多数情况下,额外的努力是不值得的。

此外,人们开始痴迷/摆弄数字,而不是关注测试的质量。我见过覆盖率超过 90% 的糟糕测试,就像我见过只有 60-70% 覆盖率的优秀测试一样。

同样,我倾向于将覆盖率视为绝对没有经过测试的指标。

于 2009-03-30T03:02:40.673 回答
19

仅仅因为存在代码覆盖率并不意味着您实际上正在测试通过该函数的所有路径。

例如,这段代码有四个路径:

if (A) { ... } else { ... }
if (B) { ... } else { ... }

然而,只有两个测试(例如,一个 A 和 B 为真,一个 A 和 B 为假)将给出“100% 的代码覆盖率”。

这是一个问题,因为一旦达到神奇的 100% 数字就会停止测试。

于 2009-03-30T02:21:08.263 回答
19

以我的经验,代码覆盖工具的最大问题是有人会成为“高代码覆盖率”等于“良好测试”的信念的牺牲品。大多数覆盖率工具只提供语句覆盖率指标,而不是条件、数据路径或决策覆盖率。这意味着可以在如下代码中获得 100% 的覆盖率:

for (int i = 0; i < MAX_RETRIES; ++i) {
    if (someFunction() == MAGIC_NUMBER) {
        break;
    }
}

...没有在 for 循环上测试终止条件。

更糟糕的是,可以从简单地调用您的应用程序的测试中获得非常高的“覆盖率”,而无需费心验证输出或验证不正确。

简而言之,代码覆盖率肯定表明测试不足,但覆盖率并不表明测试充分或正确。

于 2009-03-30T02:28:21.443 回答
5

有时极端情况非常罕见,不值得测试,但严格的代码覆盖规则要求您无论如何都要对其进行测试。

例如,在 Java 中,MD5 算法是内置的,但从技术上讲,可能会抛出“不支持的算法”类型的异常。它永远不会被抛出,您的测试将不得不经历重大的回转来测试该路径。

这会浪费很多工作。

于 2009-03-30T02:18:38.207 回答
5

在我看来,一个团队衡量代码覆盖率的最大危险是它奖励大型测试,惩罚小型测试。如果您可以选择编写一个覆盖应用程序大部分功能的单个测试,还是编写十个测试单个方法的小测试,那么仅测量代码覆盖率意味着您应该编写大型测试。

但是,编写一组 10 个小测试将使您的测试不那么脆弱,并且会比一个大型测试更彻底地测试您的应用程序。因此,通过测量代码覆盖率,尤其是在测试习惯仍在不断发展的组织中,您通常会设置错误的激励措施。

于 2009-03-30T03:23:17.800 回答
3

我知道这不是您问题的直接答案,但是...

任何测试,无论是什么类型,本身都是不够的。单元测试/代码覆盖率适用于开发人员。QA 仍然需要对整个系统进行测试。业务用户仍然需要对系统进行整体测试。

相反,QA 完全测试代码,因此开发人员不应该测试同样糟糕。测试是免费的,不同的测试提供不同的东西。每种测试类型都可能遗漏其他人可能发现的东西。

就像其他开发一样,不要走捷径进行测试,它只会让错误通过。

于 2009-03-30T02:43:43.963 回答
2
  1. 编写过于有针对性的测试用例。
  2. 代码的输入可变性测试不足
  3. 执行了大量的人工测试用例。
  4. 不专注于因噪音而导致的重要测试失败。
  5. 难以分配缺陷,因为来自许多组件的许多条件必须相互作用才能执行生产线。

拥有 100% 覆盖率目标的最坏副作用是花费大量的测试开发周期 (75%+) 来解决极端情况。这种策略的另一个不良影响是集中在特定的代码行上,而不是解决输入范围。我真的不在乎 strcpy 函数至少运行了一次。我真的很关心它是否与各种各样的输入相冲突。有政策是好的。但任何极端严厉的政策都是不好的。100% 的代码覆盖率指标对于代码被认为是可靠的既不是必要的,也不是充分的。

于 2009-03-30T02:20:27.280 回答
2

代码覆盖率的最大缺陷之一是人们只谈论代码覆盖率,而没有具体说明他们在谈论什么类型的代码覆盖率。C0、C1、C2 甚至更高级别的代码覆盖率的特性都大相径庭,所以仅仅谈论“代码覆盖率”甚至没有意义。

例如,实现 100% 的全路径覆盖几乎是不可能的。如果您的程序有n决策点,则需要 2 n次测试(并且根据定义,值中的每一位都是一个决策点,因此要为仅添加两个ints 的极其简单的函数实现 100% 完整路径覆盖,您需要 18446744073709551616 次测试)。如果你只有一个循环,你已经需要无限多的测试。

OTOH,实现 100% 的 CO 覆盖率是微不足道的。

要记住的另一件重要事情是,代码覆盖率不会告诉您测试了哪些代码。它只告诉你运行了什么代码!您可以自己尝试一下:采用具有 100% 代码覆盖率的代码库。从测试中删除所有断言。现在代码库仍然有 100% 的覆盖率,但没有测试任何东西!所以,代码覆盖率不会告诉你测试了什么,只告诉你什么没有测试。

于 2009-03-30T09:58:26.103 回答
2

有一些工具,比如 Jumble,它通过分支覆盖执行分析,通过改变你的代码来查看你的测试是否在所有不同的排列上都失败了。

直接从他们的网站:

Jumble 是一个类级别的突变测试工具,与 JUnit 一起工作。变异测试的目的是提供测试用例有效性的度量。对要测试的代码执行单个突变,然后执行相应的测试用例。如果修改后的代码未能通过测试,那么这会增加对测试的信心。相反,如果修改后的代码通过了测试,这表明存在测试缺陷。

于 2009-04-01T01:10:50.863 回答
1

代码覆盖率没有问题——我看到的错误是 100% 的数字。在某些时候,收益递减定律开始发挥作用,测试最后 1% 的成本比测试其他 99% 的成本更高。代码覆盖率是一个有价值的目标,但常识还有很长的路要走。

于 2009-03-30T02:22:22.003 回答
1

!00% 的代码覆盖率意味着经过良好测试的代码完全是一个神话。作为开发人员,我们知道系统的硬/复杂/精细部分,我更愿意看到这些区域经过适当测试,并且只获得 50% 的覆盖率,而不是每行至少运行一次的无意义数字。

就一个真实世界的例子而言,我所在的唯一一个覆盖率为 100% 的团队编写了一些我见过的最糟糕的代码。100% 的覆盖率被用来代替代码审查——结果可以预见的很糟糕,以至于大多数代码都被丢弃了,即使它通过了测试。

于 2009-03-30T02:42:17.587 回答
1

我们有很好的工具来衡量单元测试的代码覆盖率。因此,依靠 100% 的代码覆盖率来表示您已经“完成测试”是很诱人的。这不是真的。

正如其他人所提到的,100% 的代码覆盖率并不能证明您已经进行了充分的测试,50% 的代码覆盖率也不一定意味着您没有进行充分的测试。

衡量测试执行的代码行数只是一个指标。您还必须测试各种合理的函数输入,以及函数或类的行为取决于其他一些外部状态。例如,某些代码的功能会根据数据库或文件中的数据而有所不同。

我最近也在博客上写过这个:http: //karwin.blogspot.com/2009/02/unit-test-coverage.html

于 2009-03-30T02:51:43.757 回答
1

100% 的代码覆盖率并不意味着你已经完成了 usnit 测试

function int divide(int a, int b) {
    return a/b;
}

只需 1 个单元测试,我就获得了 100% 的代码覆盖率:

return divide(4,2) == 2;

现在,没有人会争辩说这个具有 100% 覆盖率的单元代码表明他的功能工作得很好。

我认为代码覆盖率是一个很好的元素,可以知道您是否缺少任何明显的代码路径,但我会小心使用它。

于 2009-03-30T03:52:17.997 回答