testing php applications

Mutation Testing Orientation

Mutation testing checks whether tests can detect small deliberate mistakes. A mutation tool changes source code in controlled ways, runs relevant tests, and reports whether the suite caught the change. In PHP, Infection is a commonly used mutation-testing tool.

Killed And Escaped Mutants

A killed mutant causes a test failure, which means the suite noticed the seeded defect. An escaped mutant leaves the suite green and points to a potentially weak assertion or missing scenario. Not every escaped mutant deserves a new test, but each deserves review.

Run It After The Fast Suite Is Healthy

Mutation testing is slower than ordinary testing. Use it on important source directories, in scheduled CI, or while improving critical rules. Infection currently requires a coverage driver such as Xdebug, phpdbg, or PCOV.

PHP example
<?php

declare(strict_types=1);

use PHPUnit\Framework\TestCase;

function freeDelivery(int $basketTotalPence): bool
{
    return $basketTotalPence >= 5_000;
}

final class FreeDeliveryTest extends TestCase
{
    public function testDeliveryBecomesFreeAtTheThreshold(): void
    {
        $this->assertFalse(freeDelivery(4_999));
        $this->assertTrue(freeDelivery(5_000));
    }
}

If a mutator changes >= to >, the threshold assertion fails and kills that mutant.

./infection.phar --threads=4

Common Mistakes

  • Running mutation testing before the ordinary suite is reliable.
  • Treating mutation score as a target without reviewing escaped mutants.
  • Adding low-value assertions only to kill a mutant.
  • Running the entire application when a focused source directory would give useful feedback faster.

What To Practise

  • Explain killed and escaped mutants.
  • Recognise a boundary mutation such as >= becoming >.
  • Use mutation results to improve meaningful tests.

Practice

Practice: Kill A Boundary Mutation

Identify tests that catch an accidental change from >= 5000 to > 5000 in a free-delivery rule.

Requirements

  • Use the value immediately below the threshold.
  • Use the exact threshold.
  • Explain which example kills the mutant.
  • Keep the rule-level test fast.
Show solution

The exact threshold detects the changed comparison operator.

PHP example
<?php

declare(strict_types=1);

function freeDelivery(int $basketTotalPence): bool
{
    return $basketTotalPence >= 5_000;
}

$examples = [4_999 => false, 5_000 => true];

foreach ($examples as $total => $expected) {
    echo freeDelivery($total) === $expected ? 'pass' : 'fail';
    echo PHP_EOL;
}

// Prints:
// pass
// pass

If >= were mutated to >, the threshold example would fail and kill the mutant.