2 Mar 2021
Hello! Today I would like to share Code Coverage Analysis configuration for PHP(Symfony) project. Then I'm going to demonstrate integration with Github Actions.
Code Coverage Analysis is important for understanding quality of tests, and how much they are testing code. More information about Code Coverage Analysis.
Initially I configured Code Coverage Analysis for this blog. Then I also made it for one of my open source projects - placeholder-service. Idea behind is to make it more clear and share all configs.
Firstly we need to install Xdebug. It can be installed different way depends on environment. Docker environment:
FROM php:8.0-fpm
# ... #
RUN pecl install xdebug
RUN docker-php-ext-enable xdebug
Local Linux Ubuntu 20.04 environment:
sudo apt install php8.0-xdebug
Then we need to add additional params for phpunit to generate code coverage report.
./bin/phpunit --coverage-html coverage --coverage-clover coverage.xml
We will add --coverage-html coverage
parameter for saving html report to coverage directory.
And --coverage-clover coverage.xml
parameter for saving xml report.
We will use it for generating code coverage badge. It also might be useful for loading coverage to separate service.
We can add shortcut to composer.json
. Then we will only need to run composer test
.
{
...
"scripts": {
...
"test": [
"./bin/phpunit --coverage-html coverage --coverage-clover coverage.xml"
],
...
}
...
}
Finally, we will have such report. There is a general coverage percent and statistics for each specific file. Uncovered lines are marked as red, so we can analyze this report and add missing tests.
Code coverage badge can be generated with PHPCoverageBadge library. Installation:
composer require --dev jaschilz/php-coverage-badger
Generate badge based on coverage statistics:
vendor/bin/php-coverage-badger coverage.xml .github/badges/coverage.svg
We also can add shortcut to composer.json
. Then we will only need to run composer update-badges
.
{
...
"scripts": {
...
"update-badges": [
"vendor/bin/php-coverage-badger coverage.xml .github/badges/coverage.svg"
],
...
}
...
}
Then let's add badge to README.md
. It won't work for a private repository, but we can save badge on external web-server in this case.
...
![Code Coverage](https://raw.githubusercontent.com/antonshell/placeholder-service/master/.github/badges/coverage.svg)
Now we will setup Code Coverage Analysis for Github Actions. Then we could see code coverage report for each Pull Request, and set required coverage percentage. Firstly need to add Xdebug to PHP installation:
# https://github.com/shivammathur/setup-php (community)
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-versions }}
coverage: xdebug
tools: composer:v2
extensions: mbstring, xml, ctype, iconv, intl, pdo_sqlite, dom, filter, gd, iconv, json, mbstring, pdo, xdebug
env:
update: true
Then we will add check to .github/workflows/tests.yml
workflow.
It will check that code coverage greater or equal then 40%.
We will use coverage.xml
file, that was generated on tests run.
- name: Check test coverage
id: test-coverage
uses: johanvanhelden/gha-clover-test-coverage-check@v1
with:
percentage: "40"
filename: "coverage.xml"
We also going to have detailed code coverage report for each Pull Request. Probably we could somehow use artifacts storage for that. Or 3rd party service, like codecov.io (which is paid). Or we can use our own external web server.
For a private repository it's very important to protect web server with basic auth or other methods!
## —— Upload tests coverage report to remote server
- name: Extract branch name
shell: bash
run: echo "##[set-output name=branch;]$(echo ${GITHUB_REF#refs/heads/})"
id: extract_branch
- name: Print branch name
shell: bash
run: echo ${{ steps.extract_branch.outputs.branch }}
- name: Create directories for reports
uses: appleboy/ssh-action@master
env:
BRANCH: ${{ steps.extract_branch.outputs.branch }}
FILES_PATH: ${{ secrets.GA_FILES_PATH }}
with:
envs: BRANCH,FILES_PATH
host: ${{ secrets.REMOTE_HOST }}
username: ${{ secrets.REMOTE_USER }}
key: ${{ secrets.SERVER_SSH_KEY }}
passphrase: ''
script: mkdir -p $FILES_PATH/$BRANCH/coverage
- name: Uploads reports to server
uses: appleboy/scp-action@master
with:
host: ${{ secrets.REMOTE_HOST }}
username: ${{ secrets.REMOTE_USER }}
key: ${{ secrets.SERVER_SSH_KEY }}
passphrase: ''
rm: true
source: "coverage"
target: ${{ secrets.GA_FILES_PATH }}/${{ steps.extract_branch.outputs.branch }}/coverage
We will extract branch name, then connect to Web server over SSH and create target directory.
Then we will save html report to the target directory. THere is a double /coverage/coverage
because otherwise branch directory will be deleted.
We also need to add GA_FILES_PATH
variable in Github Secrets storage.
We will save a path to target directory on Web server, like /var/www/files/placeholder-service
.
And we also need REMOTE_HOST
, REMOTE_USER
, SERVER_SSH_KEY
variables for SSH connection.
There are more details in my previous article or
in this tutorial.
After workflow will be completed, report will be available by link: http://files.antonshell.me/github-actions/placeholder-service/configure_code_coverage/coverage/coverage.
Now we need to show generated report on Pull Request page.
We can't do it directly, however we can add a link to generated report.
We will create .github/workflows/pull_request_comments.yml
workflow, that will post comment to Pull Request.
name: Pull request comments
on:
pull_request:
jobs:
pull_request_comments:
runs-on: ubuntu-20.04
steps:
- name: Extract branch name
shell: bash
run: echo "##[set-output name=branch;]$(echo ${GITHUB_HEAD_REF})"
id: extract_branch
- name: Print branch name
shell: bash
run: echo ${{ steps.extract_branch.outputs.branch }}
- name: Add test coverage report comment
uses: mshick/add-pr-comment@v1
with:
message: |
Test coverage report: [http://private.antonshell.me/github-actions/antonshell/${{ steps.extract_branch.outputs.branch }}/coverage/coverage](http://private.antonshell.me/github-actions/antonshell/${{ steps.extract_branch.outputs.branch }}/coverage/coverage)
repo-token: ${{ secrets.GITHUB_TOKEN }}
repo-token-user-login: 'github-actions[bot]' # The user.login for temporary GitHub tokens
allow-repeats: false # This is the default
Demo project is available on github. There is also Pull Request with code coverage implementation. That's all for today. Thank you for your attention!