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:
- If the class has a
@covers
annotation, do nothing; - 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>