Il mutation testing, spiegato
Spec: ISO/IEC/IEEE 29119-4 ISO/IEC/IEEE 29119-4 Spec: PHPUnit PHPUnit Evidence: Test-backed
In sintesi
Sezione intitolata “In sintesi”La copertura per riga indica che una riga è stata eseguita durante la suite di test. Non indica che un test avrebbe avuto esito negativo se quella riga fosse stata errata. Il mutation testing colma questa lacuna alterando deliberatamente il codice e verificando se i test rilevano la modifica. Questa pagina spiega che cosa significa un mutation score e come NextPDF lo usa come strumento diagnostico, non come trofeo.
Perché è importante
Sezione intitolata “Perché è importante”La copertura è la metrica a cui ci si affida di più nel testing, e anche una delle più fuorvianti. Un test che chiama un metodo senza asserire nulla esegue ogni riga al suo interno: copertura perfetta, rilevamento nullo. La letteratura normativa afferma esplicitamente che l’ordinamento tra criteri di copertura non dà alcuna indicazione sulla loro capacità di esporre i difetti. Tale capacità è la proprietà che essa chiama test effectiveness (ISO/IEC/IEEE 29119-4, §C.2.4). Una percentuale di copertura e una garanzia di individuazione dei difetti sono affermazioni diverse.
Per un motore PDF questo non è un discorso accademico. Un controllo del byte-range di una firma, un offset di riferimento incrociato, un ramo di codifica: i test possono “coprire” completamente tutti questi elementi senza mai asserire il valore che conta. Una suite verde su test deboli è peggiore di una lacuna dichiarata, perché scoraggia attivamente dal guardare più a fondo.
La versione breve
Sezione intitolata “La versione breve”- Il mutation testing applica migliaia di piccole modifiche deliberate (mutanti) al codice sorgente: trasforma un
<in<=, un+in-, un valore direturn— e riesegue i test contro ciascuna di esse. - Se un test fallisce su un mutante, il mutante è eliminato: qualche test ha effettivamente asserito quel comportamento. Se tutti i test continuano a passare, il mutante è sfuggito: il comportamento è stato eseguito ma mai verificato.
- Il Mutation Score Indicator (MSI) è, in linea di massima, il rapporto tra mutanti eliminati e totale dei mutanti non equivalenti. Misura se i test rilevano le modifiche, non se eseguono il codice.
- Alcuni mutanti sono equivalenti: non possono alterare il comportamento osservabile, quindi nessun test può eliminarli. Conteggiarli come mancati rilevamenti sarebbe disonesto. NextPDF li dimostra e li registra in un libro mastro anziché liquidarli in modo informale.
- NextPDF usa l’MSI per individuare e rafforzare i test deboli. È un gate diagnostico nell’integrazione continua, non un dato di marketing.
Come NextPDF lo affronta
Sezione intitolata “Come NextPDF lo affronta”Il mutation testing viene eseguito sul motore con il mutator Infection. È configurato sull’albero del codice sorgente di produzione, con le famiglie di mutator aritmetici, booleani, conditional-boundary, di uguaglianza, di valore di ritorno e di rimozione abilitate — esattamente gli operatori che espongono la logica “eseguita ma non asserita”. Il flusso è meccanico:
- Start green The suite must pass before mutation begins.
- Mutate Apply one small, deliberate change to the source.
- Re-run Run the tests that cover the mutated line.
- Killed A test failed — the behaviour is genuinely asserted.
- Escaped All tests still pass — a weak spot to strengthen.
- Equivalent No test can kill it because behaviour is unchanged — proven and ledgered, not scored as a miss.
Due scelte progettuali rendono affidabile il numero. Primo, lo score è configurato come gate. L’integrazione continua impone un MSI minimo (e un covered-MSI minimo) ed esegue una variante con ambito ristretto al diff sulle righe modificate. Di conseguenza, una modifica che aggiunge codice ma non asserzioni reali viene individuata in fase di revisione, non scoperta più tardi. Secondo, NextPDF non esclude in silenzio i mutanti scomodi. I mutanti autenticamente semanticamente equivalenti — per esempio !== rispetto a != quando la tipizzazione stretta garantisce che entrambi gli operandi condividono un tipo — vengono registrati in un libro mastro di mutation con un test esplicito di prova di equivalenza. Di conseguenza, il conteggio degli sfuggiti riflette lacune reali, non contabilità. PHPStan Level 10 più strict_types più le proprietà tipizzate è ciò che rende solide quelle prove di equivalenza.
Che cosa dicono le evidenze
Sezione intitolata “Che cosa dicono le evidenze”Evidence: Test-backed Il mutation testing è configurato nel motore sulle directory del codice sorgente di produzione con le famiglie di mutator che rivelano il comportamento abilitate. È imposto come gate di integrazione continua con un MSI minimo e una variante con ambito ristretto al diff. È un controllo di build, non un controllo aggiunto a posteriori.
Evidence: Test-backed Il problema dei mutanti equivalenti è gestito onestamente. I mutanti semanticamente equivalenti sono classificati e supportati da test dedicati di prova di equivalenza in un libro mastro di mutation; la solidità di ciascuna prova poggia su PHPStan Level 10 più la tipizzazione stretta. Il conteggio degli sfuggiti rappresenta quindi comportamento reale non rilevato, non rumore non eliminabile trasformato in uno score artificialmente peggiore.
Evidence: Standard-backed Il mutation testing è una tecnica riconosciuta, non un’invenzione di NextPDF. Spec: ISO/IEC/IEEE 29119-4, §B.2.4 ISO/IEC/IEEE 29119-4 §B.2.4 descrive l’applicazione di mutazioni generiche a elementi di una specifica per derivare mutazioni specifiche per il testing. La tecnica è necessaria proprio perché lo stesso standard afferma che l’ordinamento per sussunzione dei criteri di copertura non li ordina in base alla capacità di esporre i difetti (ISO/IEC/IEEE 29119-4, §C.2.4).
Evidence: Standard-backed La copertura in sé è ben definita e limitata. Spec: PHPUnit PHPUnit distingue la copertura per riga, per ramo e per percorso. La copertura per riga registra soltanto che una riga eseguibile è stata eseguita. Conoscerne la definizione è ciò che ne rende evidente l’insufficienza.
Esempio pratico
Sezione intitolata “Esempio pratico”Il punto non è il comando: è ciò che segnala un mutante sfuggito:
<?php
declare(strict_types=1);
final class ByteRange{ // Suppose the production guard is: // if ($offset < 0) { throw new InvalidByteRange(); } public function assertNonNegative(int $offset): void { if ($offset < 0) { throw new InvalidByteRange('offset must be >= 0'); } }}
// A test that EXECUTES this line but does not assert the boundary:// $byteRange->assertNonNegative(5); // no exception expected, none asserted// gives 100% line coverage of assertNonNegative().//// Mutation flips `< 0` to `<= 0`. Behaviour now differs ONLY at $offset === 0.// If no test passes 0 and asserts what happens, every test still passes:// the mutant ESCAPED. Coverage said "tested"; mutation said "the boundary// is unasserted". The fix is a test that pins offset === 0, not a higher// target.//// composer mutation:diff → mutate only changed lines, enforce min MSI// composer mutation:full → full-tree mutation gateQuel mutante sfuggito è l’intera proposta di valore. Ha individuato un’asserzione reale, specifica e mancante che un report di copertura considerava pienamente testata.
Equivoco comune
Sezione intitolata “Equivoco comune”L’equivoco principale è che il mutation score sia un voto da massimizzare. Un MSI molto elevato ottenuto scrivendo test per eliminare i mutanti è vuoto quanto una copertura elevata ottenuta chiamando metodi senza asserire. La metrica è stata aggirata e non misura più il rilevamento. NextPDF usa l’MSI per individuare i test deboli. Il risultato atteso è un’asserzione migliore; vantarsi non è lo scopo dichiarato.
Il secondo equivoco è che ogni mutante sopravvissuto sia un difetto nei test. Alcuni mutanti sono autenticamente equivalenti e non possono essere eliminati da alcun test, perché non alterano il comportamento osservabile. Trattarli come mancati rilevamenti produce uno score disonesto e artificialmente basso e abitua le persone a ignorare il report. La risposta di NextPDF è dimostrare esplicitamente l’equivalenza e registrarla a libro mastro, non sopprimerla silenziosamente né fingere che il numero sia peggiore di quanto sia.
Limiti e confini
Sezione intitolata “Limiti e confini”Il mutation testing misura se i test rilevano le modifiche iniettate. Non dimostra che il codice sia corretto. Non misura le prestazioni né la conformità. Non può eliminare un mutante autenticamente equivalente. Il mutation score corrente, la soglia di MSI minimo in vigore, il numero di equivalenti registrati a libro mastro e qualsiasi dato di copertura sono segnali di qualità vivi, generati dagli artefatti di integrazione continua e pubblicati con la build. Sono deliberatamente assenti qui, perché un numero incollato nella prosa diventa obsoleto e si trasforma in una piccola falsità. L’unico fatto stabile dichiarato in questa pagina è PHPStan Level 10, e si tratta di una proprietà di configurazione che sorregge le prove di equivalenza, non di una misurazione.
La selezione dei mutator, le soglie e la policy del libro mastro dipendono dalla configurazione di mutation del motore e possono evolvere. Tale configurazione è l’autorità qualora dovesse mai discordare da questa pagina. Qui non si avanza alcuna affermazione sulla test effectiveness di qualsiasi altra libreria.
Documenti correlati
Sezione intitolata “Documenti correlati”- La piramide di testing di NextPDF — i cinque livelli di test che il mutation testing verifica per il rilevamento reale dei difetti.
- Tipi stretti, ovunque — come PHPStan Level 10 e la tipizzazione stretta rendono solide le prove dei mutanti equivalenti.
- Golden-file testing — un altro livello la cui capacità di rilevamento il mutation testing aiuta a validare.
Glossario
Sezione intitolata “Glossario”- Mutante — una singola piccola modifica deliberata al codice sorgente, usata per verificare se la suite di test rileverebbe quella modifica.
- Mutante eliminato — un mutante che ha fatto fallire almeno un test; il comportamento è stato autenticamente asserito.
- Mutante sfuggito — un mutante che ha lasciato passare tutti i test. Il comportamento è stato eseguito ma mai asserito — un punto debole da correggere.
- Mutante equivalente — un mutante che non può alterare il comportamento osservabile, quindi nessun test può eliminarlo. NextPDF li dimostra e li registra a libro mastro.
- MSI (Mutation Score Indicator) — in linea di massima, i mutanti eliminati divisi per il totale dei mutanti non equivalenti; una misura del rilevamento, non dell’esecuzione.
- Copertura per riga — una metrica che registra soltanto che una riga eseguibile è stata eseguita durante la suite; definita da PHPUnit e insufficiente di per sé.