代码覆盖的陷阱

时间:2009-03-30 02:04:23

标签: unit-testing code-coverage

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

我注意到最近在工作中发生了这种情况,因为有一项政策可以实现100%的代码覆盖率。代码质量肯定在提高,但相反,测试人员似乎正在编写更宽松的测试计划,因为“代码完全经过单元测试”。因此,一些逻辑错误成功。它们是一个非常难以调试的因为“代码完全经过单元测试”。

我认为这部分是因为我们的工具只进行了声明覆盖。不过,它本来可以花更多时间。

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

提前致谢。

编辑:感谢所有非常好的回复。有一些我会将其标记为答案,但遗憾的是我只能标记一个。

13 个答案:

答案 0 :(得分:57)

在一个句子中:代码覆盖率会告诉您未经测试的内容,而不是所拥有的内容。

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

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

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

此外,人们开始迷恋/摆弄数字,而不是关注测试的质量。我看过写得不好的测试,覆盖率达到90%以上,就像我看到的只有60-70%覆盖率的优秀测试一样。

同样,我倾向于将覆盖率视为未经测试的指标。

答案 1 :(得分:18)

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

例如,此代码有四个路径:

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

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

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

答案 2 :(得分:18)

根据我的经验,代码覆盖工具的最大问题是,有人会认为“高代码覆盖率”等于“良好测试”这一信念的风险。大多数覆盖工具只提供语句覆盖度量,而不是条件,数据路径或决策覆盖。这意味着可以在这样的代码上获得100%的覆盖率:

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

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

更糟糕的是,只需调用您的应用程序就可以获得非常高的“覆盖率”,而无需费心验证输出或验证输出错误。

简单地说,代码覆盖率水平肯定表明测试不充分,但覆盖水平 表示充足或正确的测试。

答案 3 :(得分:5)

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

例如,在Java中,MD5算法是内置的,但从技术上讲,可能会抛出“不支持的算法”类型异常。它永远不会抛出,你的测试必须通过显着的旋转来测试这条路径。

浪费了很多工作。

答案 4 :(得分:5)

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

然而,编写10个小测试的集合将为您提供更少的脆弱测试,并且将比一个大型测试更彻底地测试您的应用程序。因此,通过测量代码覆盖率,特别是在具有不断发展的测试习惯的组织中,您通常可能会设置错误的激励措施。

答案 5 :(得分:3)

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

任何测试,无论何种类型,本身都是不够的。单元测试/代码覆盖是针对开发人员的。质量保证仍需要对整个系统进行测试。业务用户仍然需要对整个系统进行测试。

相反,QA完全测试代码,因此开发人员不应该测试同样糟糕。测试是免费的,不同的测试提供不同的东西。每种测试类型都可能会错过另一种测试类型。

就像开发的其余部分一样,不要在测试中使用快捷方式,它只会让错误通过。

答案 6 :(得分:2)

  1. 编写过于有针对性的测试用例。
  2. 代码的输入可变性测试不足
  3. 执行了大量人工测试用例。
  4. 不专注于因噪音引起的重要测试失败。
  5. 分配缺陷的难度很大,因为许多组件的许多条件必须相互作用才能执行。
  6. 实现100%覆盖率目标的最大副作用是花费大量测试开发周期(75%以上)引发极端情况。这种政策的另一个不良影响是集中注意特定的代码行而不是解决输入范围。我真的不在乎strcpy函数至少运行一次。我真的很在乎它与各种各样的输入相反。制定政策是好的。但是,制定任何极端严厉的政策都是不好的。代码覆盖率的100%度量既不是必需的,也不足以使代码被认为是可靠的。

答案 7 :(得分:2)

代码覆盖的最大缺陷之一是人们只是谈论代码覆盖而没有实际指定他们正在谈论的类型的代码覆盖。 C0,C1,C2的特性以及更高级别的代码覆盖率非常不同,所以只谈“代码覆盖率”甚至没有意义。

例如,实现100%全路径覆盖几乎是不可能的。如果您的程序有n个决策点,则需要进行2次 n 测试(根据定义,值中的每个位都是一个决策点,因此要实现100%的完整路径覆盖率对于一个非常简单的函数,只需添加两个int s,您需要18446744073709551616个测试)。如果您只有一个循环,那么您已经需要无限多次测试。

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

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

答案 8 :(得分:2)

有一些工具,Jumble用于通过分支覆盖执行分析,通过改变代码来查看您的测试是否因所有不同的排列而失败。

直接来自他们的网站:

  

Jumble是一个班级突变   结合使用的测试工具   与JUnit。突变的目的   测试是提供一种衡量标准的   测试用例的有效性。单身   对代码执行变异   进行测试,相应的测试   然后执行案件。如果   修改后的代码未通过测试   这增加了人们的信心   试验。相反,如果修改   代码传递了这表明的测试   测试不足。

答案 9 :(得分:1)

代码覆盖没有错 - 我看错了100%的数字。在某种程度上,收益递减规律开始出现,测试最后1%的成本比其他99%更高。代码覆盖率是一个有价值的目标,但常识还有很长的路要走。

答案 10 :(得分:1)

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

就现实世界的例子而言,我所在的唯一拥有100%覆盖率的团队写了一些我见过的最糟糕的代码。 100%的覆盖率用于替换代码审查 - 结果可预测性很差,大多数代码都被丢弃,即使它通过了测试。

答案 11 :(得分:1)

我们有很好的工具来测量单元测试的代码覆盖率。因此,依靠100%的代码覆盖率来表示您“完成了测试”是很诱人的。事实并非如此。

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

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

我最近也在博客上发表了这篇文章:http://karwin.blogspot.com/2009/02/unit-test-coverage.html

答案 12 :(得分:1)

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

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

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

return divide(4,2) == 2;

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

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