DEV Community

Cover image for Setup PHP PCOV for 5 times faster PHPUnit code coverage
Swashata Ghosh
Swashata Ghosh

Posted on

Setup PHP PCOV for 5 times faster PHPUnit code coverage

I am a WordPress developer, and my daily tasks involves writing lots of PHP code. In my latest project, I am making a GraphQL server right inside WordPress and of course it involves testing.

For code-coverage, I use XDebug and while it works, it is painfully slow. In our GitLab CI, it takes over 30 minutes to run a complete coverage.

PHPUnit Coverage with XDebug

Enter PHP PCOV. After a complete setup, our tests finish in a little over 6 minutes. It is 5 times faster than before.

PHPUnit Coverage with PCOV

Now that you might feel convinced, let's dive in how we did that.

Step 1: Installation

We need the PCOV extension installed and XDebug extension completely disabled. To install PCOV run

pecl install pcov

The above would work on most Linux and MacOS systems. If you are using official PHP docker images, then run

pecl install pcov && docker-php-ext-enable pcov

Make sure to remove XDebug as well. On docker, simply don't install XDebug extension. On other system, edit php.ini file and remove or comment the line with extension="xdebug.so".

Step 2: Clobber PHPUnit version 7 or less

PCOV has built-in support for PHPUnit version 8 or greater. But I am stuck with PHPUnit 7, because I need the full WordPress testing library which currently works only with PHPUnit 7. Luckily Joe Watkins the same person who made PCOV has also made a package called PCOV Clobber, for exactly this.

At this point, I am assuming you are using PHPUnit as a composer dependency. So run

composer require pcov/clobber --dev

followed by

vendor/bin/pcov clobber

This will make changes to the existing files inside vendor directory to make XDebug drive use PCOV instead.

Note: You have to run this command every-time you update any composer package.

Step 3: Run PHPUnit with PCOV

Now at this point, if you simply run

./vendor/bin/phpunit

chances are, you will be given empty code coverage (unless your source files are inside src, lib or app directory. So we have to make changes to some configuration variable while running PHPUnit. Simply run the following command and it should just work

php -dpcov.enabled=1 -dpcov.directory=. -dpcov.exclude="~vendor~" ./vendor/bin/phpunit --coverage-text

Thanks to Ahmet Bora at this github issue to figure this out. Here's what's happening

  • pcov.enabled=1 - Enable PCOV support.
  • pcov.directory=. - Get coverage for all files in current directory.
  • pcov.exclude="~vendor~" - Exclude coverage for vendor directory.

Bonus: GitLab CI Configuration

Since we run our tests mostly in CI, and we use GitLab, here's how our .gitlab-ci.yml file looks like.

test:php73:
  stage: test
  tags:
    - wordpress
  image: registry.gitlab.com/wpquark/docker-containers/php-node:2.0.0-php-7.3-node-12.13.0
  services:
    - mariadb:10.3
  before_script:
    - yarn install --frozen-lockfile
    - composer install
    - vendor/bin/pcov clobber
  script:
    # - yarn phpunit
    - php -dpcov.enabled=1 -dpcov.directory=. -dpcov.exclude="~vendor~" ./vendor/bin/phpunit --configuration=phpunit.ci.xml --testdox
  artifacts:
    paths:
      - coverage/phpunit
    expire_in: 2 days

The thing to notice is we do vendor/bin/pcov clobber before running the phpunit command.

Also here's the content of phpunit.ci.xml

<?xml version="1.0"?>
<phpunit
    bootstrap="tests/bootstrap.php"
    backupGlobals="false"
    colors="true"
    convertErrorsToExceptions="true"
    convertNoticesToExceptions="true"
    convertWarningsToExceptions="true"
    verbose="true"
    >
    <testsuites>
        <testsuite name="Unit Tests">
            <directory suffix="Test.php">./tests/Unit/*</directory>
        </testsuite>
        <testsuite name="Integration Tests">
            <directory suffix="Test.php">./tests/Integration/*</directory>
        </testsuite>
        <testsuite name="GraphQL Tests">
            <directory suffix="Test.php">./tests/GraphQL/*</directory>
        </testsuite>
    </testsuites>
    <filter>
        <whitelist processUncoveredFilesFromWhitelist="false">
            <directory suffix=".php">./inc</directory>
            <exclude>
                <directory suffix=".php">./inc/System</directory>
                <directory suffix=".php">./inc/View</directory>
            </exclude>
        </whitelist>
    </filter>
    <logging>
        <log type="coverage-text" target="php://stdout" showUncoveredFiles="true"/>
        <log type="coverage-clover" target="coverage/phpunit/clover.xml" showUncoveredFiles="true"/>
        <log type="coverage-html" target="coverage/phpunit/html" lowUpperBound="35" highLowerBound="70"/>
    </logging>
</phpunit>


I hope you will give PCOV a try. Also give a huge shout-out to @krakjoe who made all these things possible.

Oldest comments (1)

Collapse
 
lewiscowles1986 profile image
Lewis Cowles

Two notes. XDEBUG_MODE=off can disable xdebug.
phpunit.xml can receive a range of input under a <php> section, which allows you to set ini values, like the ones you were setting with -d.

Great write-up.