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 | classBar{publicfunctionbarMethod(){return'bar';}}classFoo{private$bar;publicfunction__construct(Bar$bar){$this->bar=$bar;}publicfunctionfooMethod(){$this->bar->barMethod();return'bar';}} |
If your test looks as follows:
1 2 3 4 5 6 7 8 9 10 | classFooTestextendsPHPUnit\Framework\TestCase{publicfunctiontest(){$bar=newBar();$foo=newFoo($bar);$this->assertSame('bar',$foo->fooMethod());}} |
PHPUnit considers a line code as tested as soon as it's been executed. TheFooTest::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 thePHPUnit 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 | <phpunitxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/6.0/phpunit.xsd"><!-- ... --><listeners><listenerclass="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><listenerclass="Symfony\Bridge\PhpUnit\CoverageListener"><arguments><string>App\Test\CoverageSolver::solve</string></arguments></listener></listeners> |