Wikipedia:
In computer science, code coverage is a measure used to describe the degree to which the source code of a program is tested by a particular test suite. A program with high code coverage has been more thoroughly tested and has a lower chance of containing software bugs than a program with low code coverage.
Testing is an unavoidable process for building a trustful software. Unfortunately, in PHP world we have a massive number of legacy software still running today that are very valuable but born in an age where testing was skipped for various reasons.
As today we are refactoring those untested systems into tested ones or we are creating new projects already focusing on having tests, we can go one step further and measure the code coverage, leveraging bug protection and code quality.
You can use these steps for a legacy project, a new project, a well-covered project, a poorly covered project; no matter the state of your project.
We are considering a PHP project using Bitbucket Pipelines as our CI and Codacy for monitoring our test coverage reports but the main concepts could be easily used when using other tools.
Table of contents:
- Dependencies installation
- PHPUnit configuration
- Set up Codacy project API token
- Pipelines configuration
1. Dependencies installation
Use composer to install dependencies:
composer require --dev phpunit/php-code-coverage codacy/coverage
Installation results would be similar to:
Using version ^1.4 for codacy/coverage
./composer.json has been updated
Loading composer repositories with package information
Updating dependencies (including require-dev)
Package operations: 3 installs, 0 updates, 0 removals
- Installing symfony/process (v5.0.4): Downloading (100%)
- Installing gitonomy/gitlib (v1.2.0): Downloading (100%)
- Installing codacy/coverage (1.4.3): Downloading (100%)
Writing lock file
Generating autoload files
ocramius/package-versions: Generating version class...
ocramius/package-versions: ...done generating version class
2. PHPUnit configuration
We need to configure at least whitelist
and logging
sections. They are required to code coverage.
Whitelist is the section that determines which files will be considered as your available code and how existent tests cover this code.
As I want that all my code loaded and analyzes by PHPUnit, I will set processUncoveredFilesFromWhitelist
. Considering that all my code is under ./src
folder:
<phpunit>
<!-- ... -->
<filter>
<!-- ... -->
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">./src</directory>
</whitelist>
</filter>
</phpunit>
Logging is where we configure logging of the test execution. Clover configuration is enough for now:
<phpunit>
<!-- ... -->
<logging>
<log type="coverage-clover" target="/tmp/coverage.xml"/>
</logging>
</phpunit>
You should now run your tests locally to ensure that you can fix everything that will be analyzed by PHPUnit. All errors have to be fixed. One example that might appear:
Fatal error: Interface 'InterfaceClass' not found in /var/www/src/Example/Application/ClassService.php on line 5
Oops.
<?php
namespace Example;
class ClassService implements InterfaceClass {
/* */
}
I have a class extending from another but I missed the import for the parent class.
<?php
namespace Example;
use Example\Domain\InterfaceClass;
class ClassService implements InterfaceClass {
/* */
}
And now I am good. After fixing all errors that might appear (and discovering some dead classes...), we test results and report generation message:
OK (10 tests, 17 assertions)
Generating code coverage report in Clover XML format ... done [5.71 seconds]
This has already configured code coverage. We will use Codacy as a tool to keep track of code coverage status, representing them in a beautiful dashboard and some other tools such as a check for new pull requests.
3. Set up Codacy project API token
For sending coverage results to Codacy, we need the project API token. This is located at Settings > Integrations tab.
If there is already a code, you can use it. Otherwise, generate one. A project token would look like something as:
a9564ebc3289b7a14551baf8ad5ec60a // not real
We will use this as an environment variable at Bitbucket. At your project in Bitbucket, go to Configurations > Pipelines > Repository Variables. In my case, I used:
Name: CODACY_PROJECT_TOKEN
Value: a9564ebc3289b7a14551baf8ad5ec60a
I want the value securely encrypted. Then I mark "Secure".
Right now we have Codacy token and the value enabled to use as an environment variable at our pipeline.
4. Pipelines configuration
For Pipelines now you should provide API token as an environment variable:
variables:
- CODACY_PROJECT_TOKEN: $CODACY_PROJECT_TOKEN
Enable xdebug:
- pecl install xdebug-2.9.2 && docker-php-ext-enable xdebug
And execute codacycoverage
to send saved report to Codacy:
- src/vendor/bin/codacycoverage clover /tmp/coverage.xml
Considering one of my legacy projects that I am adding code coverage, this could be my unit test step:
- using alpine
- source code (whitelisted) at
site/src
- logfile generated at
/tmp/unit-clover.xml
g++ gcc make git php7-dev
are required to install and enable xdebug
- step: &step-unit-tests
name: unit tests
image: php:7.2-alpine
variables:
- CODACY_PROJECT_TOKEN: $CODACY_PROJECT_TOKEN
caches:
- composer
script:
- apk add --no-cache g++ gcc make git php7-dev
- pecl install xdebug-2.9.2 && docker-php-ext-enable xdebug
- site/src/vendor/bin/phpunit -c tests/Unit/phpunit.xml
- site/src/vendor/bin/codacycoverage clover /tmp/unit-clover.xml
After being successfully executed by pipelines, you may see the results at your Codacy dashboard.
Now code with love.
And coverage.
Useful links:
- https://en.wikipedia.org/wiki/Code_coverage
- https://xdebug.org/
- https://docs.phpunit.de/en/11.3/code-coverage.html
- https://phpunit.readthedocs.io/en/8.5/code-coverage-analysis.html#whitelisting-files
- https://phpunit.readthedocs.io/en/8.5/configuration.html#the-logging-element
- https://en.wikipedia.org/wiki/Test-driven_development