Overview
I will summarise how I set up a workflow, with some optimisations to:
lint
: run QA checks, and thentest
: run test suite in a Laravel app
In my scenario I run the following steps for each job:
lint
overtrue/phplint
: lintsphp
files in parallelsquizlabs/php_codesniffer
: detects coding standard violations
test
:php artisan test
: run test suiteXDEBUG_MODE=coverage composer exec phpunit
: generate code test coverage report
I will not go into detail on the configuration for lint
steps, however for test
there are some things I will mention regarding:
phpunit.xml
- environment file
- migrations in
sqlite
Unless mentioned otherwise, the yaml
belongs in .github/workflows/test.yaml
TL;DR: gist
Triggers
|
|
Self-explanatory, the workflow will run if commit(s) pushed to main
.
You can also configure it to run on allow/deny list of branches/paths, and also tag
s. See more info on push
here.
|
|
The workflow will run if commit(s) pushed to a branch, for which there is an open PR, which has been configured to be merged into base branch main
.
This can also be configured to run on certain paths like mentioned above in push
. See here for more info.
Job to lint
This job will run first, it will do some QA checks, it is just basic linting and PSR code-sniffing for me, but you go as exotic as you feel.
|
|
Optional: composer auth for pulling in a private package
In my project I am using a private package (an API SDK to be precise, you can read more about that here):
composer.json
:
{
"name": "alistaircol/pet-store",
"repositories": [
{
"type": "github",
"url": "https://github.com/alistaircol/pet-store-api-sdk"
}
],
"require": {
"alistaircol/pet-store-api-sdk": "*@dev"
}
}
So it’s required to add COMPOSER_AUTH
, or ~/composer/auth.json
in the workflow runner.
|
|
You should create a Personal Access Token (PAT) with repo
privileges. Learn how to here.
Otherwise you will get an error like the following:
Failed to execute
git clone --mirror -- \
'git@github.com:alistaircol/pet-store-api-sdk.git' \
'/home/runner/.cache/composer/vcs/git-github.com-alistaircol-pet-store-api-sdk.git/'
Cloning into bare repository '/home/runner/.cache/composer/vcs/git-github.com-alistaircol-pet-store-api-sdk.git'...
Warning: Permanently added the ECDSA host key for IP address 'REDACTED' to the list of known hosts.
git@github.com: Permission denied (publickey).
fatal: Could not read from remote repository.
Please make sure you have the correct access rights
and the repository exists.
Job to lint… continued
|
|
The last step in the above section (with id
of composer-cache
) will retrieve the vendor
files for the composer.lock
file from cache.
This is a slight optimisation on the time spent in the workflow by not downloading the files externally, but instead retrieving them from a cache.
Notes on actons/cache
:
- Line 27: it’s required to set an
id
to access the step’soutput
in a later step - Line 30: the path to commit to cache
- Line 31: the key to restore and commit to cache
- Line 33: an ordered list of keys to use for restoring stale cache if no cache hit occurred for key
|
|
You can see the optimisation in force here. It will only run composer install
when there is no cache-hit.
|
|
You can see I am preparing for another optimisation with tj-actions/changed-files
.
A
: AddedC
: CopiedM
: ModifiedD
: DeletedR
: Renamed
For this optimisation, I only want to run the next steps when any php
file in app
and config
has been changed or modified (ACMR).
This optimisation is handy if you are building front-end task, working on documentation, or debugging workflows 😉 and have no need to run these, think of the CPU cycles saved!
|
|
Job to test
The first few steps of this job are relatively similar to the linting job above.
|
|
Having needs
as lint
, i.e. name of the first job, means that this job will only run if the lint
job has been completed successfully.
There’s no point running the test suite to check your code semantics if there is possibly syntactically incorrect code.
|
|
|
|
Here I explicitly set the extensions we need to run the test suite.
The next few steps will be similar to lint
:
|
|
With the PHP runtime, and dependencies set-up, we are almost ready to run the test suite. A couple of things worth noting before running the test suite:
- Create an empty
sqlite
database indatabase/database.sqlite
for the test suite. - It’s required to run migrations before starting tests which use
RefreshDatabase
, so I do that. - Use a very stripped down
.env.testing
and copy it to.env
, i.e.:
APP_NAME="Ally's Pet Store"
APP_ENV=testing
APP_KEY=base64:EURcoEN1DkuOyJvAMh6dzR3Y8YOI1M9WzMCUL6A7WfY=
APP_DEBUG=true
APP_URL=https://pet-store.ac93.uk
DB_CONNECTION=sqlite
Optional: note on migrations
You might run into some issues when running an alter table
query.
I was adding a new column to a table which was implicitly not nullable nullable(false)
, but didn’t have an explicit default
.
This works just fine for mysql
, and for me in sqlite
it worked, however in sqlite
in the github worker, it didn’t!
I found a solution in this stackoverflow thread:
database/migrations/whatever.php
:
|
|
There is likely some more elegant solution, but this is good enough for me, for now.
Job to test… continued
These commands will get the test suite ready:
|
|
We can now run the tests. I use php artisan test
with some --filter=PetStore
in a composer.json
’s script
section.
i.e. composer.json
:
{
"scripts": {
"tests": [
"@php artisan test --filter=PetStore"
]
}
}
The filter means that it will only run tests in tests/[Unit|Feature]/PetStore/**/*Test.php
.
|
|
The phpunit.xml
has a coverage
section added:
<?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 suffix="Test.php">./tests/Unit</directory>
</testsuite>
<testsuite name="Feature">
<directory suffix="Test.php">./tests/Feature</directory>
</testsuite>
</testsuites>
<coverage processUncoveredFiles="true">
<include>
<directory suffix=".php">./app/PetStore</directory>
</include>
<report>
<html outputDirectory="build/coverage" />
</report>
</coverage>
<php>
<server name="APP_ENV" value="testing" />
<server name="BCRYPT_ROUNDS" value="4" />
<server name="CACHE_DRIVER" value="array" />
<server name="MAIL_MAILER" value="array" />
<server name="QUEUE_CONNECTION" value="sync" />
<server name="SESSION_DRIVER" value="array" />
<server name="TELESCOPE_ENABLED" value="false" />
</php>
</phpunit>
The above highlighted lines are needed to generate a coverage report for the code in app/PetStore
to build/coverage
.
i.e. composer.json
:
{
"scripts": {
"coverage": [
"rm -rf build/coverage || :",
"XDEBUG_MODE=coverage composer exec phpunit"
]
}
}
|
|
Finally, I will create an artifact, which includes the code coverage output.
This essentially will zip
everything in build/coverage
and then it can from the workflow run page.
|
|
You could possibly add a similar optimisation from lint
to skip running tests if no php
changes have been made.
Gist
You can see gist here
You may be interested in from my previous article(s) on: