【问题标题】:Reaching 100% Code Coverage with PHPUnit使用 PHPUnit 达到 100% 的代码覆盖率
【发布时间】:2012-01-10 13:35:16
【问题描述】:

我一直在为一个项目创建一个测试套件,虽然我意识到获得 100% 的覆盖率并不是一个应该努力实现的指标,但其中有一个奇怪的地方我想澄清一下代码覆盖率报告。

看截图:

因为正在测试的方法的最后一行是return,所以最后一行(它只是一个右括号)显示为从未被执行,因此整个方法在概述。 (要么这样,要么我没有正确阅读报告。)

完整方法:

static public function &getDomain($domain = null) {
    $domain = $domain ?: self::domain();

    if (! array_key_exists($domain, self::$domains)) {
        self::$domains[$domain] = new Config();
    }

    return self::$domains[$domain];
}

这是有原因的,还是故障?

(是的,我通读了How to get 100% Code Coverage with PHPUnit,虽然相似,但大小写不同。)

编辑:

继续阅读报告,我注意到代码中其他地方的 switch 语句也是如此。所以这种行为至少在某种程度上是一致的,但让我感到困惑。

编辑2:

我正在运行:OS X 上的 PHPUnit 3.6.7、PHP 5.4.0RC5、XDebug 2.2.0-dev

【问题讨论】:

  • @Lieven 返回的是方法的最后一行,不过我也贴出了完整的方法。
  • 我看到的唯一奇怪的地方是 & b 返回一个引用。在 PHP 5.x 中,所有对象都是引用。我认为这不会导致 Xdebug 出错,它适用于 edorian,但您可能想尝试删除它。
  • @DavidHarkness 也许,但正如我在编辑中提到的那样,switch 语句也会发生同样的情况。所有案例都以break 结尾,因此应该会跳过右括号。对我来说,括号被认为是一行可运行代码对我来说似乎很奇怪。
  • 本身不是可运行的右大括号,而是通过跌落退出块。使用returncontinuebreakthrow 等会改变常规的执行流程。但是,在这种情况下,Xdebug 似乎错误地检测到不可能通过,因为 return 是无条件的。
  • 我不会在function 中说return,或者在switch 中说break 是如此不规则或如此奇怪的事件,以至于它应该破坏代码覆盖率分析器。但它已经被确定为一个错误,请参阅 Edorian 答案中的 cmets。

标签: php testing phpunit code-coverage


【解决方案1】:

首先:100% 的代码覆盖率是努力 的重要指标。这并不总是可以通过理智的努力来实现,而且这样做并不总是很重要:)

问题来自 xDebug 告诉 PHPUnit 这一行是可执行的,但没有被覆盖。

对于简单的情况,xDebug 可以判断该行不可达,因此您可以在那里获得 100% 的代码覆盖率。

请参阅下面的简单示例


第二次更新

问题现已修复 xDebug bugtracker,因此构建新版本的 xDebug 将解决这些问题 :)

更新(有关 php 5.3.x 的问题,请参见下文)

由于您运行的是 PHP 5.4 和 xDebug 的 DEV 版本,因此我已经安装了它们并对其进行了测试。我遇到了和你一样的问题,你评论的输出相同。

我不能 100% 确定问题是否来自 xDebug 的 php-code-coverage(phpunit 模块)。这也可能是 xDebug dev 的问题。

I've filed a bug with php-code-coverage 我们会找出问题出在哪里。


对于 PHP 5.3.x 问题:

对于更复杂的情况,这CAN会失败。

对于您展示的代码,我只能说“它适用于我”(下面的复杂示例)。

也许更新 xDebug 和 PHPUnit 版本,然后再试一次。

我已经看到它在当前版本中失败了,但这取决于有时整个班级的样子。

删除 ?: 运算符和其他单行多语句的东西也可能会有所帮助。

据我所知,xDebug 正在进行重构,以避免出现更多此类情况。 xDebug 曾经希望能够提供“语句覆盖”,这应该可以解决很多这样的情况。目前这里没有什么可以做的

虽然//@codeCoverageIgnoreStart//@codeCoverageIgnoreEnd 会“覆盖”这条线,但它看起来真的很丑,而且通常弊大于利。

对于发生这种情况的另一种情况,请参阅以下问题和答案:

what-to-do-when-project-coding-standards-conflicts-with-unit-test-code-coverage


简单示例:

<?php
class FooTest extends PHPUnit_Framework_TestCase {
    public function testBar() {
        $x = new Foo();
        $this->assertSame(1, $x->bar());
    }
}

<?php
class Foo {
    public function bar() {
        return 1;
    }
}

产生:

phpunit --coverage-text mep.php 
PHPUnit 3.6.7 by Sebastian Bergmann.

.

Time: 0 seconds, Memory: 3.50Mb

OK (1 test, 1 assertion)

Generating textual code coverage report, this may take a moment.

Code Coverage Report 
  2012-01-10 15:54:56

 Summary: 
  Classes: 100.00% (2/2)
  Methods: 100.00% (1/1)
  Lines:   100.00% (1/1)

Foo
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% (  1/  1)

复杂示例:

<?php

require __DIR__ . '/foo.php';

class FooTest extends PHPUnit_Framework_TestCase {

    public function testBar() {
        $this->assertSame('b', Foo::getDomain('a'));
        $this->assertInstanceOf('Config', Foo::getDomain('foo'));
    }
}

<?php

class Foo {
    static $domains = array('a' => 'b');

    static public function &getDomain($domain = null) {
        $domain = $domain ?: self::domain();
        if (! array_key_exists($domain, self::$domains)) {
            self::$domains[$domain] = new Config();
        }
        return self::$domains[$domain];
    }
}

class Config {}

产生:

PHPUnit 3.6.7 by Sebastian Bergmann.

.

Time: 0 seconds, Memory: 3.50Mb

OK (1 test, 2 assertions)

Generating textual code coverage report, this may take a moment.

Code Coverage Report 
  2012-01-10 15:55:55

 Summary: 
  Classes: 100.00% (2/2)
  Methods: 100.00% (1/1)
  Lines:   100.00% (5/5)

Foo
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% (  5/  5)

【讨论】:

  • 对于简单的情况,我们有一个匹配,但是当我运行你的复杂示例时,我得到Classes 50%, Methods: 0.00%, Lines: 83.33%。我会根据我的问题更新我的版本信息。 PS。您的测试正在访问 getDomain 作为实例方法,而该方法被声明为 static
  • 5.4RC 和开发版的 xDebug。我只用 5.3.8 和当前稳定的 xDebug 对其进行了测试。我会看看这些版本的数字是否相同
  • @nikc.org 可以复制。与 5.3.8 一起工作并与 5.4.0RC5 一起使用,无论两者何时都使用 xDebug 2.2.0-dev。我在答案中提交了错误并编辑了信息
  • @nikc.org 此问题已在 git 中修复(感谢@Derick),现在可以正常工作了 :)
  • @edorian 哇,太棒了。也感谢德里克!我想我现在必须捐款 :-)
【解决方案2】:

这里的大部分问题是坚持要获得“行”的 100% 执行覆盖率。 (管理者喜欢这个想法;这是一个他们可以理解的简单模型)。许多行不是“可执行的”(空格、函数声明之间的间隙、cmets、声明、“纯语法”,例如 switch 或类声明的结束“}”,或跨多个源代码行拆分的复杂语句)。

您真正想知道的是,“是否涵盖了所有可执行代码?”这种区别似乎很愚蠢,但却导致了解决方案。 XDebug 通过行号跟踪执行的内容,并且基于 XDebug 的方案因此报告已执行行的范围。而且您会遇到此线程中讨论的麻烦,包括必须用“不要算我” cmets 注释代码的笨拙解决方案,将“}”与最后一个可执行语句放在同一行等。没有程序员真的愿意这样做,更不用说维护它了。

如果将可执行代码定义为可以调用或由条件控制的代码(编译器人们称之为“基本块”),并且覆盖率跟踪是以这种方式完成的,那么代码的布局和愚蠢的案例就这样消失了。这种类型的测试覆盖率工具收集所谓的“分支覆盖率”,您可以通过执行所有可执行代码来获得或不获得 100% 的“分支覆盖率”。此外,它还会选择那些在一行中有条件(使用“x?y:z”)或在一行中有两个常规语句(例如,

 if  (...)  {   if  (...)  stmt1; else stmt2; stmt3 }

由于 XDebug 逐行跟踪,我相信它会将其视为一个语句,并在控制到达该行时将其视为覆盖范围,而实际上有 5 个部分需要实际测试。

我们的PHP Test Coverage tool 实现了这些想法。特别是,它知道 return 语句后面的代码是不可执行的,如果它是非空的,它会告诉你你还没有执行它。这使得 OP 的原始问题消失了。不再玩游戏来获得“真实”的覆盖率。

与所有选择一样,有时也有不利之处。我们的工具有一个只在 Windows 下运行的代码工具组件;检测的 PHP 代码可以在任何地方运行,并且处理/显示由独立于平台的 Java 程序完成。所以这对于 OP 的 OSX 系统来说可能很尴尬。检测器可以在支持 NFS 的文件系统中正常工作,因此可以说他可以在 PC 上运行检测器并检测他的 OSX 文件。

这个特殊的问题是有人试图提高他的覆盖率而提出的;恕我直言,问题是人为的,可以通过绕过人为来解决。还有另一种方法可以在不编写更多测试的情况下增加您的数字,那就是查找和删除重复代码。如果您删除重复项,则测试和测试一个(非)副本的代码会更少,效果测试(现在不存在其他副本)因此更容易获得更高的数字。你可以read more about this here.

【讨论】:

  • 我不同意。在一行上写 2 个语句是不好的做法。特别是你写的例子很恶心。我认为例外是“x?y:z”条件。
  • 这可能是一种不好的做法,但是人们编写这样的代码并且要对其进行测试。大多数测试组织都不允许仅仅为了方便或符合他们的风格意见而更改源代码。
【解决方案3】:

关于您的 switch 语句代码覆盖率问题,只需添加一个不执行任何操作的“默认”案例,您将获得完整的覆盖率。

【讨论】:

    【解决方案4】:

    以下是使 switch 语句 100% 覆盖的方法:

    确保至少有一个测试发送一个不存在的案例。

    所以,如果你有:

    switch ($name) {
        case 'terry':
            return 'blah';
        case 'lucky':
            return 'blahblah';
        case 'gerard':
            return 'blahblah';
    }
    

    确保至少有一个测试发送的名称既不是 terry 也不是 lucky 也不是 gerard

    【讨论】:

      猜你喜欢
      • 2017-12-07
      • 1970-01-01
      • 2012-05-04
      • 2012-08-04
      • 1970-01-01
      • 2011-01-28
      • 2018-05-29
      • 2012-01-18
      • 1970-01-01
      相关资源
      最近更新 更多