PHPUnit 严格模式 - setUp() - 覆盖率

16

我最近开始在PHPUnit中使用strict模式,但在测试代码覆盖率时遇到了一个问题:

如果我使用setUp方法来创建我的类的新实例,则在运行测试时__constructor方法会被列入代码覆盖范围。

这是我的测试设置:

phpunit.config.xml

<?xml version="1.0" encoding="UTF-8"?>
<phpunit
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.5/phpunit.xsd"
    bootstrap="../vendor/autoload.php"
    backupGlobals="false"
    backupStaticAttributes="false"
    colors="true"
    verbose="true"    
    beStrictAboutOutputDuringTests="true"
    beStrictAboutTestSize="true"
    beStrictAboutTestsThatDoNotTestAnything="true"
    beStrictAboutTodoAnnotatedTests="true"

    checkForUnintentionallyCoveredCode="true"
    processIsolation="false"
>
<testsuites>
    <testsuite name="FooTests">
        <directory suffix="Test.php">../tests</directory>
    </testsuite>
</testsuites>
<filter>
    <whitelist>
        <directory suffix=".php">../src</directory>
    </whitelist>
</filter>
<logging>
    <log type="coverage-html" target="coverage/" higlight="true" showUncoveredFiles="true"></log>    
</logging>

Foo.php

class Foo
{

    protected $_bar;

    public function __construct($bar)
    {
        $this->_bar=$bar;             //Line 10
    }                                 //Line 11

    public function getBar()
    {
        return $this->_bar;
    }

    public function getBar2()
    {
        return $this->_bar;
    }

}

以及测试: FooTest.php

class FooTest extends \PHPUnit_Framework_TestCase
{

    protected $_foo;

    protected function setUp()
    {
        $this->_foo=new Foo(10);
    }

    public function testGetBar()
    {
        $this->assertSame(10, $this->_foo->getBar());
    }

    /**
     * @covers Foo::getBar2
     */
    public function testGetBar2()
    {
        $this->assertSame(10, $this->_foo->getBar2());
    }

}

如果我运行测试,我会得到这个结果:

PHPUnit 4.5.0 by Sebastian Bergmann and contributors.

Configuration read from C:\xampp\htdocs\unittest\build\phpunit.config.xml

.R

Time: 88 ms, Memory: 3.50Mb

There was 1 risky test:
1) FooTest::testGetBar2
This test executed code that is not listed as code to be covered or used:
- C:\xampp\htdocs\unittest\src\Foo.php:10
- C:\xampp\htdocs\unittest\src\Foo.php:11

OK, but incomplete, skipped, or risky tests!
Tests: 2, Assertions: 2, Risky: 1.

Generating code coverage report in HTML format ... done

一旦我在测试中指定了@covers,问题就出现了。

这是预期行为吗?

我尝试过以下一些方法:

  • checkForUnintentionallyCoveredCode更改为false,显然可以解决问题,但我想使用此功能……
  • 使用processIsolation="true"也可以解决问题。但我不知道为什么?
  • @covers@uses添加到setUp()中不起作用
  • @covers添加到setUp()使用的测试中确实有效,但测试实际上并没有覆盖代码。(如果测试变得更加复杂,写起来可能很麻烦...)
  • 不同的phpunit版本:我尝试了4.34.5,结果相同
  • 不同的PHP设置:我在Win8上使用XAMPP和LinuxMint上尝试过,结果相同

是否有办法从代码覆盖率中删除setUp()代码,并在测试实际测试的方法上使用@covers

编辑:这也影响继承。所以如果Bar继承自Foo,并将参数传递给Foo::__construct,那么代码覆盖率也会包含这部分-这使得编写__construct@covers很麻烦…

附加信息:

PHP 5.6.3 (cli) (built: Nov 12 2014 17:18:08)
Copyright (c) 1997-2014 The PHP Group
Zend Engine v2.6.0, Copyright (c) 1998-2014 Zend Technologies
with Xdebug v2.2.5, Copyright (c) 2002-2014, by Derick Rethans

通常建议构造函数尽可能少做工作,事实上,如果可能的话,它们不应该做任何其他事情,只需将其参数分配给实例变量即可。简单的构造函数实际上不需要进行测试,因此它对代码覆盖率报告的影响并不重要。至于您的情况,您可以尝试仅具有空setup()方法,并在每个测试用例中执行$this object = new ObjectUnderTest()。 - GordonM
@GordonM 我想测试的唯一事情是,依赖项(数据库、记录器等)的类型提示是否设置正确。__construct方法仅执行分配操作。 - SpazzMarticus
4个回答

5
自从这个问题开始引起一些关注:这是我对问题的解决方案。
我的FooTest单元测试将始终使用Foo,因此我将@uses Foo添加到类中。
这也很重要,如果公共函数使用protected/private函数,否则,如果类在内部使用该函数,则必须将每个protected/private函数添加到测试中。我甚至认为,如果您正在进行单元测试,那么这样做是错误的,因为单元测试不必关心类如何执行操作,它只应断言特定输入导致特定输出。
(另外:构造函数只应执行分配,什么都不做。)
添加@uses后,错误将消失。
(您可以将@covers Foo::_construct添加到类中以覆盖构造函数的代码覆盖率。)
/**
 * @uses Foo 
 * (optional)@covers Foo::__construct
 */
class FooTest extends \PHPUnit_Framework_TestCase
{
    protected $_foo;

    protected function setUp()
    {
        $this->_foo=new Foo(10);
    }

    public function testGetBar()
    {
        $this->assertSame(10, $this->_foo->getBar());
    }

    /**
     * @covers Foo::getBar2
     */
    public function testGetBar2()
    {
        $this->assertSame(10, $this->_foo->getBar2());
    }
}

3
您已经指定了使用 checkForUnintentionallyCoveredCode="true" 的严格代码覆盖率。自PHPUnit 4.0以来,PHPUnit 有以下行为:

处理意外覆盖的代码

PHPUnit 4.0 可以选择性地严格控制意外覆盖的代码(严格 > 覆盖模式)。启用后,当一个测试使用 @covers 注解并执行未通过 @covers 注解指定的代码时,PHPUnit 将会失败该测试。


我完全意识到这一点。问题是:测试不执行__construct,只有setUp执行,尽管如此,我必须在每个测试中指定@covers来覆盖__construct,否则测试会被归类为“有风险”的。 - SpazzMarticus
setUp()和teardown()会为每个测试执行,所以从某种意义上来说它们不是测试的一部分吗?虽然我没有测试过,但我预计如果我在我的setup()或teardown()中使用@expectedException注释并抛出该异常,那么测试将因此而通过... - qrazi

1

phpunit.xml 中设置 PHPUnit >= 6.0 的 beStrictAboutCoversAnnotation 为 false:

<phpunit
     // ....     
     beStrictAboutCoversAnnotation="false"
>
// ....

此外,您可以在不使用--strict-coverage的情况下运行phpunit

有关风险测试:意外覆盖的代码的更多信息


0

我认为被接受的答案是不正确的。它基本上说,这个类中的任何东西都可以使用。这可能是你想要的,也可能不是你想要的。特别是如果你想确保你的testGetBar2()方法不使用其他代码,你将无法做到。

你可以做的是忽略你的设置方法所做的事情。

    /**
     * @codeCoverageIgnore
     */
    protected function setUp()
    {
        $this->_foo=new Foo(10);
    }

这样,您在设置中输入的任何内容都不会被视为测试代码,但是您明确覆盖的任何内容都将显示出来。

https://phpunit.de/manual/3.7/en/code-coverage-analysis.html#code-coverage-analysis.ignoring-code-blocks


在我的看法中,对于单元测试来说是可以的。你举例中的@codeCoverageIgnore不会起作用,因为它标记的是setUp函数而不是构造函数。可以将其添加到__construct函数中,但那样所有的测试都会被忽略。 - SpazzMarticus

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接