Introduction
This guide will help you integrate OtterWise with your PHP project to track code coverage. OtterWise supports all major PHP testing frameworks and works seamlessly with popular frameworks like Laravel, Symfony, and WordPress.
Whether you're building web applications, APIs, command-line tools, or packages, this guide covers the setup for your specific use case.
Prerequisites
Before setting up OtterWise for your PHP project, you'll need:
- A GitHub repository connected to OtterWise
- PHP installed (version 8.1 or higher recommended)
- A testing framework configured in your project
- Xdebug or PCOV extension for code coverage collection
- Your OtterWise repository token (found in your repository settings after enabling it in OtterWise)
Supported Coverage Formats
OtterWise supports the following coverage formats for PHP projects:
- Clover XML - The most common format for PHP, supported by PHPUnit and Pest
- Cobertura XML - Alternative XML format
We recommend using Clover XML format as it's the standard for PHP projects.
Coverage Drivers
PHP requires a coverage driver to collect code coverage data. The two main options are:
Xdebug
Xdebug is the most popular PHP debugging and profiling extension. It's feature-rich but can be slower for large test suites.
Install via PECL:
pecl install xdebug
Or on Ubuntu/Debian:
sudo apt-get install php-xdebug
PCOV
PCOV is a lightweight code coverage driver designed specifically for code coverage. It's much faster than Xdebug for coverage collection.
Install via PECL:
pecl install pcov
Recommendation
Use PCOV for CI environments as it's significantly faster. Use Xdebug for local development if you need debugging features.
Setup with PHPUnit
PHPUnit is the de facto standard testing framework for PHP.
Installation
Install PHPUnit via Composer:
composer require --dev phpunit/phpunit
Configuration
Create or update your phpunit.xml or phpunit.xml.dist file:
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="vendor/autoload.php"
colors="true">
<testsuites>
<testsuite name="Application Test Suite">
<directory>tests</directory>
</testsuite>
</testsuites>
<source>
<include>
<directory suffix=".php">src</directory>
</include>
</source>
<coverage>
<report>
<clover outputFile="build/logs/clover.xml"/>
</report>
</coverage>
</phpunit>
Running Tests with Coverage
Run PHPUnit with coverage:
# Using PCOV (recommended for CI) php -d pcov.enabled=1 vendor/bin/phpunit --coverage-clover=build/logs/clover.xml # Using Xdebug XDEBUG_MODE=coverage vendor/bin/phpunit --coverage-clover=build/logs/clover.xml # If configured in phpunit.xml vendor/bin/phpunit
Uploading to OtterWise
After generating the coverage report, upload it to OtterWise using the bash uploader:
# Using environment variable export OTTERWISE_TOKEN=your-repo-token bash <(curl -s https://raw.githubusercontent.com/getOtterWise/bash-uploader/main/uploader.sh) # Or pass token directly bash <(curl -s https://raw.githubusercontent.com/getOtterWise/bash-uploader/main/uploader.sh) --repo-token your-repo-token
Setup with Pest
Pest is an elegant PHP testing framework with a focus on simplicity. It's built on top of PHPUnit.
Installation
composer require --dev pestphp/pest --with-all-dependencies
Configuration
Pest uses the same phpunit.xml configuration as PHPUnit. Alternatively, you can specify coverage options directly:
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="vendor/autoload.php"
colors="true">
<testsuites>
<testsuite name="Test Suite">
<directory>tests</directory>
</testsuite>
</testsuites>
<source>
<include>
<directory suffix=".php">app</directory>
<directory suffix=".php">src</directory>
</include>
<exclude>
<directory>app/Providers</directory>
</exclude>
</source>
<coverage>
<report>
<clover outputFile="build/logs/clover.xml"/>
</report>
</coverage>
</phpunit>
Running Tests with Coverage
# Generate coverage report vendor/bin/pest --coverage --coverage-clover=build/logs/clover.xml # With minimum coverage enforcement vendor/bin/pest --coverage --min=80
Framework-Specific Setup
Laravel
Laravel projects can use either PHPUnit or Pest. Laravel includes PHPUnit by default.
Using PHPUnit (default)Laravel includes a phpunit.xml file by default. Update it to include coverage reporting:
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="vendor/autoload.php"
colors="true">
<testsuites>
<testsuite name="Unit">
<directory>tests/Unit</directory>
</testsuite>
<testsuite name="Feature">
<directory>tests/Feature</directory>
</testsuite>
</testsuites>
<source>
<include>
<directory>app</directory>
</include>
</source>
<coverage>
<report>
<clover outputFile="build/logs/clover.xml"/>
</report>
</coverage>
<php>
<env name="APP_ENV" value="testing"/>
<env name="BCRYPT_ROUNDS" value="4"/>
<env name="CACHE_DRIVER" value="array"/>
<env name="DB_CONNECTION" value="sqlite"/>
<env name="DB_DATABASE" value=":memory:"/>
<env name="MAIL_MAILER" value="array"/>
<env name="QUEUE_CONNECTION" value="sync"/>
<env name="SESSION_DRIVER" value="array"/>
</php>
</phpunit>
Run tests:
# Using artisan command php artisan test --coverage-clover=build/logs/clover.xml # Or directly with PHPUnit XDEBUG_MODE=coverage vendor/bin/phpunit --coverage-clover=build/logs/clover.xmlUsing Pest
Install Pest for Laravel:
composer require --dev pestphp/pest --with-all-dependencies composer require --dev pestphp/pest-plugin-laravel php artisan pest:install
Run tests:
vendor/bin/pest --coverage --coverage-clover=build/logs/clover.xml # With artisan (Laravel 10.48+) php artisan test --coverage --coverage-clover=build/logs/clover.xml
Symfony
Symfony projects use PHPUnit with the Symfony PHPUnit Bridge.
Install dependencies:
composer require --dev symfony/test-pack composer require --dev symfony/phpunit-bridge
Create or update phpunit.xml.dist:
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="tests/bootstrap.php"
colors="true">
<php>
<ini name="display_errors" value="1"/>
<ini name="error_reporting" value="-1"/>
<server name="APP_ENV" value="test" force="true"/>
<server name="SHELL_VERBOSITY" value="-1"/>
<server name="SYMFONY_PHPUNIT_REMOVE" value=""/>
<server name="SYMFONY_PHPUNIT_VERSION" value="9.5"/>
</php>
<testsuites>
<testsuite name="Project Test Suite">
<directory>tests</directory>
</testsuite>
</testsuites>
<source>
<include>
<directory suffix=".php">src</directory>
</include>
</source>
<coverage>
<report>
<clover outputFile="build/logs/clover.xml"/>
</report>
</coverage>
</phpunit>
Run tests:
# Using Symfony PHPUnit Bridge XDEBUG_MODE=coverage php bin/phpunit --coverage-clover=build/logs/clover.xml # Or with PCOV php -d pcov.enabled=1 bin/phpunit --coverage-clover=build/logs/clover.xml
WordPress
WordPress plugins and themes can use PHPUnit with the WordPress test library.
Install dependencies:
composer require --dev phpunit/phpunit composer require --dev yoast/phpunit-polyfills
Create phpunit.xml.dist:
<?xml version="1.0"?>
<phpunit
bootstrap="tests/bootstrap.php"
colors="true">
<testsuites>
<testsuite name="Plugin Tests">
<directory>tests</directory>
</testsuite>
</testsuites>
<source>
<include>
<directory suffix=".php">includes</directory>
</include>
</source>
<coverage>
<report>
<clover outputFile="build/logs/clover.xml"/>
</report>
</coverage>
</phpunit>
Run tests:
XDEBUG_MODE=coverage vendor/bin/phpunit --coverage-clover=build/logs/clover.xml
CI Integration Examples
GitHub Actions
Here's a complete example for a PHP project using GitHub Actions with the official OtterWise action:
name: Tests
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 2
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: 8.3
coverage: pcov
extensions: mbstring, pdo, pdo_mysql
- name: Get Composer Cache Directory
id: composer-cache
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- name: Cache Composer packages
uses: actions/cache@v3
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
restore-keys: ${{ runner.os }}-composer-
- name: Install dependencies
run: composer install --prefer-dist --no-progress
- name: Run tests with coverage
run: vendor/bin/phpunit --coverage-clover=build/logs/clover.xml
- name: Upload Coverage to OtterWise
uses: getOtterWise/github-action@v1
with:
token: ${{ secrets.OTTERWISE_TOKEN }}
Important
Remember to add your OTTERWISE_TOKEN to GitHub Secrets (Settings > Secrets and variables > Actions).
GitHub Actions with Laravel
name: Laravel Tests
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
services:
mysql:
image: mysql:8.0
env:
MYSQL_ROOT_PASSWORD: password
MYSQL_DATABASE: test_db
ports:
- 3306:3306
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 2
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.3'
coverage: pcov
extensions: mbstring, pdo, pdo_mysql
- name: Copy .env
run: php -r "file_exists('.env') || copy('.env.example', '.env');"
- name: Install Dependencies
run: composer install --prefer-dist --no-progress
- name: Generate key
run: php artisan key:generate
- name: Directory Permissions
run: chmod -R 777 storage bootstrap/cache
- name: Run tests with coverage
env:
DB_CONNECTION: mysql
DB_HOST: 127.0.0.1
DB_PORT: 3306
DB_DATABASE: test_db
DB_USERNAME: root
DB_PASSWORD: password
run: php artisan test --coverage-clover=build/logs/clover.xml
- name: Upload Coverage to OtterWise
uses: getOtterWise/github-action@v1
with:
token: ${{ secrets.OTTERWISE_TOKEN }}
GitLab CI
test:
image: php:8.3
stage: test
before_script:
- apt-get update -yqq
- apt-get install -yqq git libzip-dev
- pecl install pcov
- docker-php-ext-enable pcov
- curl -sS https://getcomposer.org/installer | php
- php composer.phar install
script:
- php -d pcov.enabled=1 vendor/bin/phpunit --coverage-clover=build/logs/clover.xml
- bash <(curl -s https://raw.githubusercontent.com/getOtterWise/bash-uploader/main/uploader.sh) --repo-token $OTTERWISE_TOKEN
coverage: '/^\s*Lines:\s*\d+\.\d+\%/'
artifacts:
reports:
coverage_report:
coverage_format: cobertura
path: build/logs/clover.xml
CircleCI
version: 2.1
jobs:
test:
docker:
- image: cimg/php:8.3
steps:
- checkout
- restore_cache:
keys:
- v1-dependencies-{{ checksum "composer.lock" }}
- run:
name: Install Dependencies
command: composer install --prefer-dist --no-progress
- save_cache:
paths:
- ./vendor
key: v1-dependencies-{{ checksum "composer.lock" }}
- run:
name: Install PCOV
command: |
sudo pecl install pcov
sudo docker-php-ext-enable pcov
- run:
name: Run Tests with Coverage
command: php -d pcov.enabled=1 vendor/bin/phpunit --coverage-clover=build/logs/clover.xml
- run:
name: Upload Coverage to OtterWise
command: bash <(curl -s https://raw.githubusercontent.com/getOtterWise/bash-uploader/main/uploader.sh) --repo-token $OTTERWISE_TOKEN
workflows:
version: 2
test:
jobs:
- test
Quick Reference: Coverage Commands
| Framework | Command | Output Location |
|---|---|---|
| PHPUnit | vendor/bin/phpunit --coverage-clover=build/logs/clover.xml |
build/logs/clover.xml |
| Pest | vendor/bin/pest --coverage-clover=build/logs/clover.xml |
build/logs/clover.xml |
| Laravel (Artisan) | php artisan test --coverage-clover=build/logs/clover.xml |
build/logs/clover.xml |
| Laravel (Pest) | vendor/bin/pest --coverage-clover=build/logs/clover.xml |
build/logs/clover.xml |
| Symfony | php bin/phpunit --coverage-clover=build/logs/clover.xml |
build/logs/clover.xml |
Using Composer Scripts
You can add convenient scripts to your composer.json for running tests with coverage:
{
"scripts": {
"test": "vendor/bin/phpunit",
"test:coverage": "XDEBUG_MODE=coverage vendor/bin/phpunit --coverage-clover=build/logs/clover.xml",
"test:coverage:html": "XDEBUG_MODE=coverage vendor/bin/phpunit --coverage-html=build/coverage",
"test:pcov": "php -d pcov.enabled=1 vendor/bin/phpunit --coverage-clover=build/logs/clover.xml"
}
}
Then run with:
composer test:coverage
Troubleshooting
Common Issues and Solutions
| Issue | Solution |
|---|---|
| No coverage driver available |
|
| Coverage file not found |
|
| Low or zero coverage |
|
| Tests are very slow |
|
| Memory exhausted errors |
|
Debugging Coverage Generation
To verify your coverage is being generated correctly:
# Run coverage locally XDEBUG_MODE=coverage vendor/bin/phpunit --coverage-clover=build/logs/clover.xml # Check if the file exists ls -la build/logs/clover.xml # View the first few lines to verify format head -20 build/logs/clover.xml # Generate HTML report for visual inspection XDEBUG_MODE=coverage vendor/bin/phpunit --coverage-html=build/coverage open build/coverage/index.html
A valid Clover XML file should start like this:
<?xml version="1.0" encoding="UTF-8"?>
<coverage generated="...">
<project timestamp="...">
<file name="/path/to/file.php">
<line num="1" type="stmt" count="1"/>
...
Advanced Configuration
Parallel Test Execution
For faster test execution, use ParaTest:
composer require --dev brianium/paratest # Run tests in parallel with coverage vendor/bin/paratest --coverage-clover=build/logs/clover.xml -p4
Coverage Filtering
Exclude specific files or directories from coverage in phpunit.xml:
<source>
<include>
<directory suffix=".php">src</directory>
</include>
<exclude>
<directory>src/Migrations</directory>
<directory>src/Generated</directory>
<file>src/bootstrap.php</file>
</exclude>
</source>
Path Coverage
PHPUnit 10+ supports path coverage (more accurate than line coverage):
<coverage pathCoverage="true">
<report>
<clover outputFile="build/logs/clover.xml"/>
</report>
</coverage>
Additional Upload Options
The OtterWise bash uploader supports several options for advanced use cases. See the Bash Uploader documentation for complete details.
Common options for PHP projects:
# Specify custom coverage file location bash <(curl -s https://raw.githubusercontent.com/getOtterWise/bash-uploader/main/uploader.sh) \ --repo-token $OTTERWISE_TOKEN \ --file ./build/logs/clover.xml # Add a flag for test suite identification bash <(curl -s https://raw.githubusercontent.com/getOtterWise/bash-uploader/main/uploader.sh) \ --repo-token $OTTERWISE_TOKEN \ --flag unit-tests # For monorepos - track different packages separately bash <(curl -s https://raw.githubusercontent.com/getOtterWise/bash-uploader/main/uploader.sh) \ --repo-token $OTTERWISE_TOKEN \ --file ./packages/core/build/logs/clover.xml \ --flag core
Next Steps
Once you have coverage data uploading successfully, you can:
- Configure Pull Request comments to see coverage changes in your PRs
- Set up status checks to enforce coverage thresholds
- Enable line annotations to highlight uncovered code in PRs
- Use badges to display your coverage status in your README
For additional help, check our example repository or contact OtterWise support.