使用PHPUnit实现100%的代码覆盖率

时间:2012-01-10 13:35:17

标签: php testing phpunit code-coverage

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

见截图:

enter image description here

因为正在测试的方法的最后一行是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语句也是如此。所以这种行为至少在某种程度上是一致的,但对我来说却令人困惑。

EDIT2:

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

4 个答案:

答案 0 :(得分:36)

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

问题来自于xDebug告诉PHPUnit这行是可执行的但未涵盖。

对于简单的情况,xDebug可以告诉该行无法访问,因此您可以获得100%的代码覆盖率。

请参阅下面的简单示例


第二次更新

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

更新(请参阅下面的php 5.3.x问题)

由于您运行的是PHP 5.4和xDebug的DEV版本,我已经安装了这些并进行了测试。我使用您评论的相同输出遇到了与您相同的问题。

如果问题来自x php-code-coverage(phpunit模块)xDebug,我不是100%肯定。它也可能是xDebug开发的一个问题。

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)

答案 1 :(得分:4)

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

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

如果将可执行代码定义为可以调用或由条件(编译器人称之为“基本块”)控制的代码,并且覆盖跟踪以这种方式完成,那么代码的布局和愚蠢的案子简直消失了。这种类型的测试覆盖工具收集所谓的“分支覆盖”,您可以通过执行所有可执行代码来获得或不获得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 :(得分:1)

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

答案 3 :(得分:0)

以下是如何使switch语句100%覆盖:

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

所以,如果你有:

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

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