Contributed by
Grégoire Pineau
in #23149.

Code coverage 是一个标尺,它描述的是程序的源代码被某特定测试组件测试后的级别。代码覆盖率高,即已被彻底测试,包含 bug 的机会就小。PHPUnit提供了 工具来测量 code coverage,但并不足够精确人。

思考下例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Bar
{
    public function barMethod() { return 'bar'; }
}
 
class Foo
{
    private $bar;
 
    public function __construct(Bar $bar)
    {
        $this->bar = $bar;
    }
 
    public function fooMethod()
    {
        $this->bar->barMethod();
 
        return 'bar';
    }
}

如果你的测试是下面这样:

1
2
3
4
5
6
7
8
9
10
class FooTest extends PHPUnit\Framework\TestCase
{
    public function test()
    {
        $bar = new Bar();
        $foo = new Foo($bar);
 
        $this->assertSame('bar', $foo->fooMethod());
    }
}

PHPUnit 认为一行代码应在执行之后马上得到测试。FooTest::test 执行的是 FooBar 类的每一行代码,因此 PHPUnit 所计算出来的代码覆盖率将会是 100%。然而这并不准确,因为 Bar 类并未得到测试。

解决方案是对每一个类使用 PHPUnit 的 @covers annotation 来指定哪个类是当前测试到的。因此 在 Symfony 3.4 中我们把一个 CoverageListener 添加到了 PHPUnit Bridge 组件中以提供 更佳的 code coverage reports.

你要做的唯一改变,是在 phpunit.xml 配置文件中,让程序一个 CoverageListener 作为 PHPUnit 的一个监听:

1
2
3
4
5
6
7
8
9
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/6.0/phpunit.xsd"
>
    <!-- ... -->
 
    <listeners>
        <listener class="Symfony\Bridge\PhpUnit\CoverageListener" />
    </listeners>
</phpunit>

此监听检查每一个待测试的类,并做如下事情:

  1. 若类有一个 @covers annotation,什么也不做;
  2. 若未找到 @covers annotation,在这个类中自动寻找测试代码并添加注释。

用于寻找测试代码的逻辑是基于 Symfony 的最佳实践: 测试时使用的是和代码相同的目录结构,再添加一个 Test 后缀到类名中。例如,若测试的类是 My\Namespace\Tests\FooTest,则相关的类会按照 My\Namespace\Foo 进行猜测。

如果这个猜测逻辑太简单,或在你的程序中无法使用,你可以对 PHPUnit listener 提供自己的解决方案:

1
2
3
4
5
6
7
<listeners>
    <listener class="Symfony\Bridge\PhpUnit\CoverageListener">
        <arguments>
            <string>App\Test\CoverageSolver::solve</string>
        </arguments>
    </listener>
</listeners>