php[architect] logo

Want to check out an issue? Sign up to receive a special offer.

Automated Refactoring With Rector

Posted by on August 18, 2023

In our previous article, we discussed how to get started using code coverage to determine if our code has enough tests and one of the things I mentioned is how we should integrate this into our CI/CD pipeline but I didn’t say how.

This articles exists to rectify that problem by providing you with two options to integrate code coverage into your CI/CD pipeline.

A Simple Script

The simplest way to solve this problem is to add a simple script to our repository that checks the percentage of our code coverage versus our goal percentage.

To do this we first need to set up our run of PHPUnit to export a coverage file using the `–coverage-clover` command line argument. This file is an XML file that contains a line-by-line output of what files are covered by tests and which aren’t. It’s an interesting file to look at if you’re curious but it can be quite large and is only there so other programs can consume this information.

Next, we need to create a script to read this value vs our required value. Because this is a PHP-focused channel we’ll use PHP for this but you can use your preferred language.

We’re going to want to make the coverage percentage easily editable so we’ve made it an argument to the script so we can update it in our active file.


// make sure we have all three arguments
if ($argc != 3) {
echo "Usage: " . $argv[0] . " ";
exit(-1);
}

$file = $argv[1];
$threshold = (double) $argv[2];

$coverage = simplexml_load_file($file);
$ratio = (double) ($coverage->project->metrics["coveredstatements"] / $coverage->project->metrics["statements"] * 100);

echo "Line coverage: $ratio%";
echo "Threshold: $threshold%";

if ($ratio < $threshold) { echo "FAILED!"; exit(-1); } echo "SUCCESS!";

Now we can integrate this into our GitHub Action for our Value Object project. To do this I'll include the file in my repository. I like to put it into a "scripts" directory so it's not cluttering the root of my project.

Then we add this as a step to our PHPUnit job.


name: PHP Checks

on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]

permissions:
contents: read

jobs:
phpunit:

runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
php-versions: ['8.0', '8.1']

steps:
- uses: actions/checkout@v3

- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-versions }}
extensions: mbstring, dom, fileinfo, mysql
coverage: xdebug

- name: Validate composer.json and composer.lock
run: composer validate --strict

- name: Cache Composer packages
id: composer-cache
uses: actions/cache@v3
with:
path: vendor
key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }}
restore-keys: |
${{ runner.os }}-php-

- name: Install dependencies
run: composer install --prefer-dist --no-progress

- name: PHPUnit Tests
uses: php-actions/phpunit@v3
env:
XDEBUG_MODE: coverage
with:
bootstrap: vendor/autoload.php
configuration: phpunit.xml
php_extensions: xdebug
args: tests --coverage-clover ./coverage.xml
- name: Coverage Check
run: php ./scripts/phpunit-threshold.php ./coverage.xml 90

I'll push this to GitHub in a new branch and see how it affects a pull request we've created.

Now there are two downsides to this simple script.

The first is that we're only looking at the total code coverage and not how the current pull request affects this number. Because of this it's easy to have our total code coverage drift downward over time because we're not checking to see if this PR is a net positive or negative for our code coverage.

The other is that it's a challenge to determine what else should have code coverage because it's a simple pass/fail.

CodeCov

The solution to our problems from earlier is to use an external solution that will track our code coverage over time, compare how our PR affects code coverage and provides integration into our review software to show code that's not covered by a test.

Now there are a **ton** of tools that do this but I wanted to quickly set up an example using [Codecov](https://about.codecov.io/). They're not a sponsor and I'm picking them because of my previous usage of them and the fact that they provide free results to open repositories on GitHub.

First set up an account on the Codecov website the process is very simple if you log in with your GitHub account.

You'll be brought to a screen that contains a list of your repositories. Click on the "setup repo" link on the line with your repository.

On the next screen, we'll be given our CODECOV_TOKEN which we need to copy into the secret section of our GitHub Repository.

Then we'll click on the link to let us install Codecov's application into our repository. This will allow it to post back status messages, comment on our pull requests, and become a check inside our pull requests.

Next, we're going to edit our action to upload our coverage report to Codecov by adding the codecov/codecov-action action to our YML file.


name: PHPUnit

on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]

permissions:
contents: read

jobs:
build:

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3
- name: Cache Composer packages
id: composer-cache
uses: actions/cache@v3
with:
path: vendor
key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }}
- name: Install dependencies
run: composer install --prefer-dist --no-progress

- name: PHPUnit Tests
uses: php-actions/phpunit@v3
env:
XDEBUG_MODE: coverage
with:
bootstrap: vendor/autoload.php
configuration: phpunit.xml
php_extensions: xdebug
args: tests --coverage-clover ./coverage.xml

- name: Upload to Codecov
uses: codecov/codecov-action@v2
with:
token: ${{ secrets.CODE_COV_TOKEN }}
files: ./coverage.xml
verbose: true

Finally, when we push our main branch we'll get a report back on the Codecov website showing the current level. 25% is really bad so let's fix that!

I'm going to create a new branch so we can see how CodeCov shows the results inside of a Pull Request.

I'll create a new branch so we can track the changes on the new branch.

Next, we'll create tests/LastNameTest.php to test our LastName class. It's the same as our FirstNameTest but using the LastName class.


namespace Tests;

use PHPUnit\Framework\TestCase;
use ScottValueObjects\LastName;

class LastNameTest extends TestCase
{
public function testCanInitialize(): void
{
$this->assertNotNull(new LastName("Name"));
}
}

Finally, we'll commit this new file, push this branch, and create a pull request.

After the tests run, Codecov will report back on how the PR will affect the total percentage.

As a maintainer of the repository, I'll use this information to decide if I should accept this pull request. If the pull increases coverage or at the very least keeps it equal I can easily accept it. If I see a large negative change in coverage it's a bad sign and should be sent back.

It also shows us what lines aren't covered by the change so we can jump in and fix those before they end up lowering our project's code coverage total.

Code Coverage Badge

If you spend any time looking at open-source projects on GitHub you'll notice a lot of them have these cool badges to show of well they're meeting their code coverage requirements. We can have the same things with CodeCov.

What You Need to Know

* CodeCov provides a solution to validate our code coverage
* Integrates with GitHub Pull Request process
* Easy to setup


Tags:
 

Leave a comment

Use the form below to leave a comment: