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
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
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.