Glossario · Fondamenti del testing
Mutation testing
Detto anche: test di mutazione, analisi delle mutazioni, mutation score, mutation analysis
Definizione
Tecnica che inserisce nel codice piccoli bug deliberati (mutanti) e riesegue i test per misurare se la suite rileva davvero i difetti.
Il mutation testing (test di mutazione) mette alla prova i tuoi test. Uno strumento crea molte copie del codice, ognuna con una piccola modifica, ad esempio >= trasformato in >, + in -, oppure il corpo di un metodo sostituito con return null. Ogni copia modificata è un mutante. La suite di test viene eseguita contro ogni mutante. Se almeno un test fallisce, il mutante è ucciso (killed). Se tutti i test passano comunque, il mutante è sopravvissuto (survived): significa che i test non si accorgerebbero di quel bug.
Mutation score = mutanti uccisi / mutanti totali (esclusi i mutanti equivalenti).
Esempio
boolean isAdult(int age) {
return age >= 18;
}
Test: isAdult(30) è true, isAdult(10) è false. La copertura di righe e di branch è al 100%.
Lo strumento crea un mutante con age > 18. Entrambi i test passano ancora, quindi il mutante sopravvive. Questo rivela un test sul valore limite mancante: nessuno ha verificato isAdult(18). Aggiungerlo uccide il mutante. La coverage non poteva dirtelo; il mutation testing sì.
Strumenti
- Java: PIT (pitest), con plugin per Maven e Gradle e supporto a JUnit 5.
- JavaScript/TypeScript: StrykerJS, che esiste anche in versione .NET e Scala.
- Python: mutmut e Cosmic Ray.
Limiti pratici
- Costo. Ogni mutante è un’esecuzione dei test. Gli strumenti riducono il costo eseguendo solo i test che coprono la riga mutata e lavorando in modo incrementale sul codice modificato, ma resta comunque molto più lento di un’esecuzione normale. La maggior parte dei team lo esegue di notte o sui file modificati in una pull request, non a ogni commit sull’intera codebase.
- Mutanti equivalenti. Alcune modifiche non alterano affatto il comportamento, ad esempio trasformare
i < nini != nin un ciclo in cuiicresce sempre di uno partendo da un valore inferiore an. Nessun test può ucciderli, quindi vanno analizzati ed esclusi a mano. - Dà il meglio a livello unit. Il mutation testing è pensato per unit test veloci e deterministici. Eseguirlo su test UI lenti raramente è praticabile.
Usa i mutanti sopravvissuti come lista di cose da fare per le asserzioni mancanti, soprattutto nella logica di business, invece di inseguire un punteggio perfetto.