Mutation testing con Infection
Misuri quanto sono realmente efficaci i suoi test
Mutation testing con Infection è una lezione PHP Academy gratuita su CoddyKit. Questa è la lezione 4 di 4. Puoi leggere la lezione completa qui gratuitamente — poi esercitati direttamente nel browser con un editor di codice integrato e un tutor IA disponibile 24/7. Fa parte del percorso di apprendimento PHP Academy, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso PHP Academy include 4 lezioni in totale.
La coverage inganna
Una coverage delle righe del 100% sembra rassicurante, ma dimostra soltanto che i test hanno eseguito il codice, non che sarebbero in grado di rilevare un bug al suo interno. Un test può eseguire una riga senza verificare nulla di significativo. Il mutation testing misura ciò che conta davvero: i test fallirebbero se il codice fosse danneggiato in modo sottile? Infection è lo strumento PHP standard per farlo.
composer require --dev infection/infectionL'idea fondamentale: i mutanti
Infection prende il codice coperto dai test e vi introduce piccoli difetti: i mutanti. Trasforma > in >=, + in -, && in ||, rimuove un return e così via. Poi riesegue i test su ogni mutante:
- Se un test fallisce → il mutante viene eliminato (bene: i test hanno rilevato il difetto).
- Se tutti i test passano → il mutante è sopravvissuto (male: un bug reale in quel punto passerebbe inosservato).
Un mutante sopravvissuto in azione
Consideri questa funzione e un test debole. Infection trasformerebbe >= in >. Se nessun test verifica il limite esatto (quando l'importo è uguale alla soglia), il mutante sopravvive, rivelando un caso limite non testato.
<?php
function qualifiesForFreeShipping(float $total): bool
{
return $total >= 50.0; // Infection mutates >= to >
}
// Weak test only checks 100 and 10 -> never tests exactly 50.0
var_dump(qualifiesForFreeShipping(100.0)); // true
var_dump(qualifiesForFreeShipping(10.0)); // false
var_dump(qualifiesForFreeShipping(50.0)); // true <-- the boundary the mutant exposes
Eliminare il mutante
Aggiungendo l'asserzione sul limite, il mutante >=-> viene eliminato: con il codice modificato, 50.0 > 50.0 è false, quindi il test fallisce, esattamente come desideriamo. Il mutation testing indica letteralmente quali asserzioni mancano.
<?php
use PHPUnit\Framework\TestCase;
final class ShippingTest extends TestCase
{
public function test_threshold_is_inclusive(): void
{
// Kills the >= -> > mutant
self::assertTrue(qualifiesForFreeShipping(50.0));
}
}
Configurazione: infection.json
Infection viene gestito tramite infection.json5. Qui si dichiarano le directory da mutare, la destinazione dei log e le soglie minime del punteggio necessarie per superare la CI. Imposti source.directories solo sul codice di produzione, mai sui test.
{
"source": {
"directories": ["src"]
},
"logs": {
"text": "build/infection.log",
"html": "build/infection.html"
},
"mutators": {
"@default": true
},
"minMsi": 80,
"minCoveredMsi": 90
}Le metriche MSI
Infection restituisce il Mutation Score Indicator:
- MSI = mutanti eliminati / mutanti totali. Viene penalizzato dal codice non coperto (i relativi mutanti non possono essere eliminati).
- Covered MSI = mutanti eliminati / mutanti sulle righe coperte. Misura la qualità delle asserzioni nei punti effettivamente testati.
- Mutation Code Coverage = quanta parte del codice Infection è riuscito a mutare.
Una coverage delle righe elevata ma un Covered MSI basso è il segnale tipico di test con poche asserzioni significative.
Eseguire Infection in modo efficiente
Il mutation testing è costoso: la suite viene rieseguita una volta per ogni mutante. Due importanti ottimizzazioni sono eseguire i test in parallelo con --threads e mutare solo il codice modificato dal branch corrente usando il filtraggio delle differenze Git, una soluzione ideale per la CI sulle pull request.
vendor/bin/infection --threads=max --git-diff-lines --git-diff-base=origin/mainAnalizzare i mutanti sopravvissuti
Il valore sta nel diff che Infection stampa per ogni mutante sopravvissuto. Mostra la riga esatta e la modifica che i test non sono riusciti a rilevare. Consideri i mutanti sopravvissuti come una lista di attività: aggiunga l'asserzione mancante oppure riconosca che il mutante è innocuo (un mutante equivalente).
- return $total >= 50.0;
+ return $total > 50.0;
# Mutant survived: no test asserts the inclusive boundary (total === 50.0)Mutanti equivalenti e soppressione
Alcuni mutanti sono equivalenti: modificano il codice, ma non il suo comportamento osservabile, quindi nessun test potrebbe mai eliminarli. Non possono essere eliminati e abbassano ingiustamente l'MSI. Sopprima i mutatori noti per produrre falsi positivi sul codice specifico, invece di manipolare i test per inseguirli.
{
"mutators": {
"@default": true,
"Plus": {
"ignore": ["App\\Math\\Statistics::variance"]
}
}
}Dove il mutation testing è più utile
Poiché è lento, lo applichi in modo mirato:
- Lo applichi alla logica fondamentale del dominio: prezzi, autorizzazioni e calcoli, dove i bug silenziosi sono costosi.
- Imposti come requisito per le pull request il Covered MSI delle righe modificate, non quello dell'intero repository.
- Non insegua il 100% a livello globale: i rendimenti decrescenti e i mutanti equivalenti lo rendono uno spreco.
- Lo usi per individuare le asserzioni deboli, poi corregga i test: il punteggio è un mezzo, non l'obiettivo.
Per essere veloce, richiede i dati di coverage
Infection modifica solo le righe effettivamente coperte dai test, quindi riutilizza la coverage del test runner. Con Xdebug è lento; pcov è molto più veloce per la coverage delle righe ed è il driver consigliato per le esecuzioni di mutation testing. Infection può anche generare autonomamente la coverage oppure utilizzare un report già prodotto in CI.
# Faster mutation runs: use pcov instead of Xdebug for coverage
php -d pcov.enabled=1 vendor/bin/infection --threads=max
# Or reuse coverage already generated by your PHPUnit step:
vendor/bin/infection --coverage=build/coverage --skip-initial-testsVerifica rapida
Che cosa indica un mutante sopravvissuto?
Riepilogo
Ha imparato a misurare la qualità dei test, non solo la quantità:
- Il mutation testing inserisce piccoli difetti (mutanti); eliminato = difetto rilevato, sopravvissuto = lacuna.
- La coverage mostra l'esecuzione; Covered MSI mostra la solidità delle asserzioni.
- La configurazione avviene tramite
infection.json5con soglie MSI; per velocizzare l'esecuzione, usi--threadse il filtraggio basato sulle differenze Git. - I mutanti sopravvissuti sono una lista di attività; presti attenzione ai mutanti equivalenti e li ignori deliberatamente.
- Punti alla logica fondamentale e imposti come requisito per le pull request l'MSI delle righe modificate, invece di inseguire un 100% globale.
Domande Frequenti
La lezione «Mutation testing con Infection» è gratuita?
Sì — il testo completo di «Mutation testing con Infection» è gratuito qui sul web. Per esercitarvi in modo interattivo (un editor di codice integrato e un tutor IA 24/7) e sbloccare il resto del corso PHP Academy, passa a CoddyKit PRO. Il corso PHP Academy include 4 lezioni in totale.
Cosa imparerò in «Mutation testing con Infection»?
Misuri quanto sono realmente efficaci i suoi test Eserciti PHP Academy con codice pratico che esegui direttamente nel browser, e un tutor IA 24/7 risponde alle tue domande mentre lavori sulla lezione.
Ho bisogno di esperienza per iniziare PHP Academy?
Non è richiesta alcuna esperienza precedente. PHP Academy su CoddyKit è strutturato per principianti e studenti avanzati, quindi puoi iniziare da qui o dall'inizio e procedere al tuo ritmo. Questa è la lezione 4 di 4.
Quanto tempo richiede la lezione «Mutation testing con Infection»?
La maggior parte delle lezioni CoddyKit richiede circa 5–10 minuti. Ogni lezione è breve e interattiva, quindi fai progressi costanti e riprendi esattamente da dove hai lasciato su web e app.
Posso scrivere ed eseguire codice in questa lezione PHP Academy?
Sì. Ogni lezione PHP Academy include un editor di codice integrato, quindi scrivi ed esegui codice reale direttamente nel tuo browser e ricevi feedback istantaneo dall'IA — nessuna configurazione locale necessaria.
Tutte le lezioni di questo corso
- Il flusso di lavoro dello sviluppo guidato dai test
- Mock e stub con Mockery
- Test di integrazione e funzionali
- Mutation testing con Infection