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.