Grégoire Pineau
Contributed by Grégoire Pineau in #23149

Code coverage is a measure describing the degree to which the source code of a program is tested by a particular test suite. Code with high code coverage has been more thoroughly tested and has a lower chance of containing bugs. PHPUnit provides utilities to measure code coverage, but they are not precise enough.

Consider this simple example:

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';
    }
}

If your test looks as follows:

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 considers a line code as tested as soon as it's been executed. The FooTest::test method executes every line of code of the Foo and Bar classes, so the code coverage calculated by PHPUnit will be 100%. However, this is not precise because the Bar class is not really tested.

The solution is to use the PHPUnit @covers annotation on each test class to specify which class a test is testing. This solution is cumbersome and hard to maintain, so in Symfony 3.4 we've added a CoverageListener to the PHPUnit Bridge component to provide better code coverage reports.

The only change you need to make in your application is to register CoverageListener as a PHPUnit listener in the phpunit.xml config file:

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>

This listener checks every test class and does the following:

  1. If the class has a @covers annotation, do nothing;
  2. If no @covers annotation is found, find the code tested by this class automatically and add the annotation.

The logic used to find the tested code is based on Symfony's best practices: tests use the same directory structure as code and add a Test suffix to the class names. For example, if the test class is My\Namespace\Tests\FooTest, the related class is guessed as My\Namespace\Foo.

If this guessing logic is too simple or doesn't work for your application, you can provide your own solver to the 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>
Published in #Living on the edge