September 27, 2017 Javier Eguiluz

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: