什么是代码覆盖率?你如何衡量它?

什么是代码覆盖率?你如何衡量它?

有人问我这个关于自动化测试代码覆盖率的问题。似乎在自动化工具之外,它更像是艺术而不是科学。是否有任何关于如何使用代码覆盖的实际示例?

243214 次浏览

代码覆盖率只是对所测试代码的度量。有多种可以度量的覆盖标准,但通常是程序中的各种路径、条件、函数和语句构成了总覆盖。代码覆盖率度量是执行每种覆盖率标准的测试的百分比。

至于我如何跟踪项目中的单元测试覆盖率,我使用静态代码分析工具来跟踪。

代码覆盖率是在自动化测试运行时执行代码的行数/块数/弧数的度量。

代码覆盖率是通过使用专门的工具来检测二进制文件,以添加跟踪调用,并对检测的产品运行一整套自动化测试来收集的。好的工具不仅可以告诉您执行的代码的百分比,还可以让您深入数据,并查看在特定测试中执行了哪些代码行。

我们的团队使用麦哲伦 -一组内部代码覆盖工具。如果你是一个。net商店,Visual Studio有集成的工具来收集代码覆盖率。你也可以滚动一些自定义工具,就像这篇文章描述的那样。

如果你是c++商店,英特尔有一些运行于Windows和Linux的工具,尽管我没有使用过它们。我也听说GCC有gcov工具,但我对它一无所知,也不能给你一个链接。

至于我们如何使用它——代码覆盖率是我们每个里程碑的退出标准之一。我们实际上有三个代码覆盖率指标——来自单元测试的覆盖率(来自开发团队)、场景测试的覆盖率(来自测试团队)和组合覆盖率。

顺便说一句,虽然代码覆盖率是衡量你做了多少测试的一个很好的指标,但它不一定是衡量你测试产品的好坏的一个很好的指标。除了代码覆盖率,您还应该使用其他指标来确保质量。

代码覆盖率基本上告诉您测试中覆盖了多少代码。例如,如果您有90%的代码覆盖率,这意味着测试中没有覆盖10%的代码。

我知道您可能会想,如果覆盖了90%的代码,就已经足够好了,但是您必须从不同的角度看问题。是什么阻止您获得100%的代码覆盖率?

一个很好的例子是:

if(customer.IsOldCustomer())
{
}
else
{
}

现在,在上面的代码中,有两个路径/分支。如果你总是按“是”;布兰奇,你没有涵盖“其他”。部分,它将显示在代码覆盖结果。这很好,因为现在您知道了没有覆盖的内容,并且可以编写一个测试来覆盖“其他”内容。部分。如果没有代码覆盖,您就只是坐在一个定时炸弹上,等待爆炸。

NCover是一个衡量代码覆盖率的好工具。

请记住,拥有“100%代码覆盖率”并不意味着所有内容都被完全测试了——虽然这意味着每一行代码都被测试了,但这并不意味着它们在每种(常见)情况下都被测试了。

我将使用代码覆盖率来突出显示我可能应该为其编写测试的代码。例如,如果任何显示myImportantFunction()的代码覆盖工具在运行我当前的单元测试时没有执行,那么它们可能应该得到改进。

基本上,100%的代码覆盖率并不意味着您的代码是完美的。使用它作为编写更全面(单元)测试的指南。

在前面的回答中已经很好地解释了代码覆盖率。所以这更像是对问题第二部分的回答。

我们使用了三个工具来确定代码覆盖率。

  1. JTest -一个建立在JUnit之上的专有工具。(它还生成单元测试。)
  2. Cobertura -一个开源代码覆盖工具,可以很容易地与JUnit测试结合以生成报告。
  3. 艾玛 -另一个-我们使用它的目的与单元测试略有不同。当最终用户访问web应用程序时,它被用于生成覆盖率报告。这与web测试工具(例如:Canoo)相结合,可以为您提供非常有用的覆盖率报告,告诉您在典型的最终用户使用过程中覆盖了多少代码。

我们使用这些工具

  • 检查开发人员已经编写了良好的单元测试
  • 确保在黑盒测试期间遍历所有代码

对于Perl,有一个优秀的猛击:封面模块,我经常在我的模块上使用它。

如果构建和安装由Module:: build管理,你可以简单地运行./Build testcover来获得一个漂亮的HTML站点,告诉你每个子、行和条件的覆盖率,漂亮的颜色可以很容易地看到哪些代码路径没有被覆盖。

对之前的许多答案进行了几点补充:

代码覆盖率意味着您的测试集覆盖源代码的程度。也就是说,源代码在多大程度上被测试用例集所覆盖。

正如在上面的回答中提到的,有各种各样的覆盖标准,比如路径、条件、函数、语句等。但要涵盖的其他标准是

  1. 条件覆盖:计算所有布尔表达式的真和假。
  2. 决策覆盖:不仅要计算一次true和false的布尔表达式,还要覆盖所有后续if-elseif-else主体。
  3. 循环覆盖率:意味着每个可能的循环都被执行了一次,多次和零次。同样,如果我们对最大极限有假设,那么,如果可行的话,测试最大极限次数和,比最大极限次数多一次。
  4. 进入和退出范围:测试所有可能的调用及其返回值。
  5. 参数值覆盖率(PVC)。检查是否测试了参数的所有可能值。例如,字符串可以是以下任意一种:a)空,b)空,c)空白(空格,制表符,新行),d)有效字符串,e)无效字符串,f)单字节字符串,g)双字节字符串。未能测试每个可能的参数值可能会留下错误。如果只测试其中的一个选项,就会得到100%的代码覆盖率,因为每一行都被覆盖了,但是如果只测试七个选项中的一个,就意味着参数值的覆盖率只有14.2%。
  6. 继承覆盖率:对于面向对象的源,当返回由基类引用的派生对象时,如果返回了兄弟对象,则应该测试要计算的覆盖率。

注意:静态代码分析将发现是否有任何不可访问的代码或挂起的代码,即没有被任何其他函数调用覆盖的代码。还有其他静态报道。即使静态代码分析报告100%的代码被覆盖了,如果所有可能的代码覆盖率都被测试了,它也不会给出关于您的测试集的报告。

在前面的回答中,已经很好地解释了代码覆盖率。我只是添加了一些与工具相关的知识,如果你在iOSOSX平台上工作,Xcode提供了测试和监控代码覆盖率的工具。

参考链接:

https://developer.apple.com/library/archive/documentation/DeveloperTools/Conceptual/testing_with_xcode/chapters/07-code_coverage.html

https://medium.com/zendesk-engineering/code-coverage-and-xcode-6b2fb8756a51

两者都是学习和探索Xcode代码覆盖率的有用链接。

对于PHP,你应该看看Sebastian Bergmann的Github

提供PHP代码覆盖率信息的收集、处理和呈现功能。

https://github.com/sebastianbergmann/php-code-coverage

代码覆盖率测试的目的是找出有多少代码被测试。代码覆盖工具生成一个报告,其中显示了多少应用程序代码已经运行。代码覆盖率以百分比来衡量,越接近100%越好。这是一个白盒测试的例子。下面是一些用于代码覆盖率测试的开源工具:

  1. Simplecov -用于Ruby
  2. 被单 -用于。net
  3. Cobertura -用于Java
  4. Coverage.py -用于Python
  5. 开玩笑 -用于JavaScript

什么代码覆盖不是

为了真正理解什么是代码覆盖,理解它不是什么是非常重要的。

这里和有关问题的一些回答/评论都提到了这一点:

  • < p > 弗兰Penov

    顺便说一句,虽然代码覆盖率是衡量你做了多少测试的一个很好的指标,但它不一定是衡量你测试产品的好坏的一个很好的指标。

  • < p > 史蒂夫

    仅仅因为代码的每一行都在测试中的某个时刻运行,并不意味着您已经测试了代码可以在其中运行的所有可能场景。如果你有一个接受x并返回x/x的函数,并且你使用my_func(2)运行测试,你将有100%的覆盖率(因为函数的代码已经运行了),但是当参数为0时,你错过了一个巨大的问题。也就是说,即使覆盖率达到100%,你也没有测试所有必要的场景。

  • < p > KeithS:

    然而,覆盖率的另一面实际上是双重的:首先,为了覆盖率而增加覆盖率的测试是无用的;每个测试都必须证明代码在某些新情况下按预期工作。此外,“coverage"不是“锻炼”;您的测试套件可能会执行SUT中的每一行代码,但它们可能无法证明某一行逻辑在每种情况下都有效。

没有人比马克·辛普森说得更简洁、更切题了:

代码覆盖率告诉你还没测试了什么,测试了什么。

举例说明

我花了一些时间写了一个功能请求的回复伊斯坦布尔(一个Javascript测试覆盖工具)"改变覆盖范围的定义,要求超过1次命中"每一行。没有人会在那里看到它🤣,所以我认为在这里重复使用它的要点可能是有用的:

覆盖工具不能证明你的代码经过了充分的测试。它所能做的只是告诉您,您为代码库中的每一行代码提供了某种覆盖率,但即使这样,它也不能证明覆盖率有任何意义,因为测试可能执行一行代码而没有对其结果做出任何断言。只有作为开发人员的你才能决定实际的语义上独特的输入变量和需要被测试覆盖的边界条件,并确保测试逻辑实际上做出了正确的断言。

例如,假设您有以下Javascript函数。断言(1, 1)输入返回1的单个测试将为您提供100%的行覆盖率。这证明了什么?

function max(a, b) {
return a > b ? a : b
}

先把这个测试的在语义上糟糕的覆盖率放在一边,100%的行覆盖率也相当具有误导性,因为它没有提供100%的分支覆盖率。这很容易通过将分支分割到不同的行上并重新运行行覆盖报告来看到:

function max(a, b) {
if (a > b) {
return a
} else {
return b
}
}

甚至

function max(a, b) {
return a > b ?
a :
b
}

这告诉我们,“覆盖范围”;metric太依赖于实现,而在理想的情况下测试应该是黑盒。即使那样,也得凭自己的判断。

例如,以下三个输入情况是否构成max函数的完整测试?

  • (2, 1)
  • (1, 2)
  • (1, 1)

对于上述实现,您将获得100%的行和100%的分支覆盖率。但是非数字输入呢?好的,你增加了两个输入情况:

  • (null, 1)
  • (1, null)

这迫使你更新实现:

function max(a, b) {
if (typeof a !== 'number' ||  typeof b !== 'number') {
return undefined
}
return a > b ? a : b
}

看上去不错。你有100%的行和分支覆盖率,而且你已经覆盖了无效输入。

但这就足够了吗?负数呢?

100% 黑箱覆盖率的理想是一个幻想

在我看来,在情况下,对于函数的简单性质,测试负数情况是多余的。如果情况不同,假设函数的存在只是因为我们需要实现一些棘手的算法或优化,这可能会或可能不会像预期的那样对负数工作,那么我会添加更多的输入情况,包括负数。

通常情况下,您只能发现极端情况,因为您有数百或数千个用户,并且只能通过他们以意想不到的方式或在您无法预见或再现的条件和软件环境中使用您的软件,即使您可以预见或再现这种罕见的情况。通常,这些罕见的情况是由实现的本质造成的,而不是通过分析有bug的代码接口的理想化抽象得出的结果。

我认为这表明100%黑盒覆盖率的理想的有点不切实际。如果您将所有东西都当作一个理想化的黑盒,那么您将浪费大量的时间来编写不必要的测试。在上面的例子中,我知道实现使用了一个简单可靠的非数字检查,然后使用本机Javascript逻辑来比较值(a > b),并且做任何更复杂的事情都是愚蠢的。知道了这一点,我不打算测试传入负数、浮点数、字符串、对象等。

在一天结束的时候,你必须实际并使用良好的判断,而这种判断通常不能忽视对黑箱中内容的性质的了解,或者至少是黑箱中所做的假设。

所有这些都表明,我没有计算机科学学位😂。程序员建议的IANAL是什么?