CI and Mutation Testing

Running Tests in CI and Mutation Testing

On GitHub Actions 29 , shivammathur/setup-php@v2 installs PHP and a coverage driver (a database suite also needs a services: mysql block). The workflow needs a GitHub 29 repository, so it was not run here.

.github/workflows/tests.yml, the unit suite on every push and pull requestYAML
name: tests
on: [push, pull_request]
jobs:
  phpunit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - uses: shivammathur/setup-php@v2
        with: { php-version: '8.5', coverage: pcov }
      - run: composer install --no-progress
      - run: vendor/bin/phpunit --testsuite unit --coverage-text

Mutation testing asks whether the tests would catch a bug. Infection 2,244 (github.com/infection/infection (https://github.com/infection/infection 2,244 ), BSD-3-Clause, 0.35.4 of 2 September 2026, PHP 8.3+) plants small bugs (mutants), such as >= becoming >, reruns the covering tests against each, and reports survivors. On the shipping rule:

Mutation testing one class with four parallel workersPHP
vendor/bin/infection src/ShippingRule.php --threads=4 --show-mutations
Output
...
-    return $subtotal->cents >= 5000 ? new Money(0) : new Money(499);
+    return $subtotal->cents > 5000 ? new Money(0) : new Money(499);
...
7 mutations were generated:
       6 mutants were killed by Test Framework
       1 covered mutants were not detected
Metrics:
         Mutation Code Coverage: 100%
         Covered Code MSI: 85%

One mutant escaped: the boundary bug that 100% coverage missed. Add #[TestWith([5000, 0])] and the rerun kills 7 of 7, a Covered Code MSI (mutation score indicator) of 100%. On large code bases, mutate only changed files (--git-diff-filter=AM) and set a floor with --min-msi=80.