Un test flaky passa e fallisce sullo stesso codice. Tra le due esecuzioni non è cambiato niente tranne i tempi, l’ordine, o qualcosa fuori dal test a cui nessuno stava pensando.

È peggio di un test che fallisce sempre, e non è un modo di dire. Un test che fallisce sempre viene sistemato prima di pranzo. Un test che fallisce una volta su dieci insegna a un’intera squadra a premere il pulsante di riesecuzione, e una volta che quell’abitudine esiste, ogni fallimento genuino deve contendere al rumore l’attenzione di qualcuno.

Quanto costa l’instabilità

Abitua le persone a ignorare il rosso. La prima volta che una build fallisce, qualcuno indaga. Al ventesimo falso allarme la risposta è una riesecuzione e una scrollata di spalle, e una regressione vera riceverà la stessa scrollata di spalle.

Si nasconde nella folla. Una suite con una dozzina di test inaffidabili fallisce abbastanza spesso perché nessuno riesca a distinguere un fallimento inaffidabile da uno vero senza aprirlo, e quindi nessuno lo apre.

Rende la suite più lenta e meno affidabile insieme. Ogni riesecuzione costa minuti; il dubbio costa di più.

Da dove viene l’instabilità

Quasi tutta è una di cinque cose.

Aspettare la cosa sbagliata. Di gran lunga il caso più comune nei test di browser. Il test chiede se qualcosa è visibile mentre sta ancora comparendo, o verifica uno stato che l’interfaccia raggiunge una frazione di secondo dopo. Passa su una macchina veloce e fallisce su un server di build carico, che è esattamente la macchina su cui non puoi fare debug.

Dipendenza dall’ordine. Un test che passa solo dopo che ne è stato eseguito un altro, perché quello ha creato il record, impostato lo stato o lasciato qualcosa dietro di sé. Esegui la suite in un ordine diverso, o in parallelo, e crolla.

Stato condiviso. Una riga di database, un valore in cache, un file su disco, un orologio. Due test che usano la stessa fixture prima o poi verranno eseguiti abbastanza vicini da interferire.

Il tempo. Tutto ciò che verifica la data di oggi prima o poi attraversa la mezzanotte; tutto ciò che ha un timeout fallisce sotto carico; tutto ciò che dipende dall’ordine di due eventi senza garanzia di ordinamento è un lancio di moneta che non hai ancora notato.

Il mondo esterno. Un test che raggiunge una rete vera, un servizio di terze parti vero o un orologio vero ha preso in prestito la disponibilità di qualcun altro.

L’errore che ne nasconde la maggior parte

Il filo comune dei casi di tempistica merita di essere detto a parte, perché cambia il modo in cui scrivi la verifica.

Aspettare una transizione
Verificare che il pulsante mostri la sua etichetta a riposo, mentre l'animazione che ripulisce lo stato precedente è ancora in corso
Aspettare uno stato
Verificare che la classe "copied" sia sparita, e solo allora che l'etichetta a riposo sia lì

Il primo è una corsa tra la pazienza del framework di test e il timer dell’interfaccia. Passa su una macchina tranquilla e fallisce su una occupata, e il messaggio di errore è confuso invece che informativo: l’elemento è presente, con il testo giusto, e semplicemente non è ancora visibile.

Quell’esempio è reale: è un test di questa stessa base di codice, ed è fallito esattamente una volta, su un runner carico, su un commit che cambiava due file di immagine.

Cosa fare con uno

Non risolverlo aspettando di più. Alzare un timeout globale rallenta il percorso di fallimento di ogni altro test e nasconde la corsa successiva invece di eliminarla. Equivale ad alzare il volume della musica.

E non cancellarlo nemmeno, almeno non per prima cosa. Un test flaky di solito sta indicando qualcosa di reale: una corsa genuina nel prodotto, un’interfaccia che dichiara di aver finito prima di aver finito, una risorsa condivisa che due cose usano. Sistemare il test a volte significa sistemare l’applicazione.

Quarantena, poi correzione, con una scadenza. Toglilo dalla suite bloccante perché smetta di insegnare alle persone a ignorare il rosso, e mettici una data. Una quarantena senza scadenza è una cancellazione con qualche passaggio in più e la coscienza più sporca.

Contali. Una squadra che non sa dire quanti test flaky ha lo scoprirà quando il numero sarà grande. Se il tuo runner registra le riesecuzioni, quello è il numero da tenere d’occhio.

Quando l’instabilità è colpa del prodotto

A volte il test ha ragione e il software è inaffidabile: una richiesta che ogni tanto arriva fuori ordine, un’interfaccia che dice “salvato” prima che il salvataggio sia completo, un job che di solito finisce prima che la pagina si ricarichi.

Quelli sono difetti veri ed è penoso segnalarli, perché per definizione non capitano ogni volta. Ciò che li rende riproducibili per qualcun altro è il contesto dell’esecuzione fallita, e non la descrizione delle dieci che sono passate.

Session Replay

Estensione gratuita per Chrome. Un clic sulla pagina che si comporta male cattura lo screenshot, la console e il registro di rete, e ti restituisce un link da incollare nel ticket.

Installa l'estensione

Per un difetto intermittente, di’ quanto spesso capita e cosa stavi facendo quando è capitato: “tre volte su circa venti tentativi, sempre subito dopo il salvataggio” è molto più utile della descrizione di una singola occorrenza. La guida alla segnalazione di bug copre il resto.

La versione breve

Un test flaky fallisce su codice invariato, e il suo costo vero è che insegna alle persone a ignorare i fallimenti. La maggior parte sono un test che aspetta una transizione invece di uno stato, una dipendenza dall’ordine, o stato condiviso. Sistema la causa invece del timeout, metti in quarantena con una scadenza invece che a tempo indeterminato, e prendi sul serio la possibilità che il test abbia ragione e che sia il software a essere inaffidabile.