Test instabili: quali sono le cause e come risolverli definitivamente

  1. Home
  2. Blog
  3. Testare l’automazione

Automazione dei test

Test instabili: Che cosa li causa e come risolverli definitivamente

Un test che fallisce in modo casuale insegna alla tua squadra a ignorare i fallimenti. Scopri come confermare che un test è instabile, rintracciare la vera causa e risolverlo, con demo che mostrano cosa funziona effettivamente.

9 min di lettura
Test instabili: cosa li causa e come risolverli definitivamente

Punti chiave

  • Un test instabile supera e fallisce sullo stesso codice, senza alcuna modifica.
  • Le cause più comuni sono la tempistica, lo stato condiviso, i dati instabili, i servizi esterni e le differenze ambientali.
  • Sostituisci le ore di sonno fisse con le attese per una condizione specifica e fornisci a ogni test i propri dati.
  • I nuovi tentativi possono nascondere veri bug. Metti in quarantena un test instabile, correggilo entro una scadenza o eliminalo.

La build diventa rossa, qualcuno fa clic su “riesegui”, passa e tutti vanno avanti. Sembra innocuo, ma i test instabili distruggono lentamente la cosa più preziosa che una suite di test possiede: la fiducia delle persone in essa. Quando i fallimenti sono solitamente rumore, vengono segnalati anche bug reali.

Questa guida illustra il motivo per cui i test diventano instabili e come risolvere correttamente ciascuna causa. Abbiamo eseguito le demo di timing e stato condiviso in Python 3.13 e i numeri mostrati provengono da quelle esecuzioni. Si tratta di piccole dimostrazioni progettate per rendere facile vedere la fragilità, quindi i tassi di fallimento saranno diversi.

Cos’è un test traballante?

Un test instabile fornisce risultati diversi sullo stesso codice. Passa in un’esecuzione e fallisce in quella successiva, anche se non è cambiato nulla nel prodotto. Il test potrebbe essere difettoso oppure il codice potrebbe contenere un vero bug intermittente. Questa seconda possibilità è esattamente il motivo per cui non dovresti semplicemente ignorarli.

Perché i test instabili sono così costosi

  • Perdita di fiducia. Una volta che le persone presumono che il rosso significhi “probabilmente traballante”, smettono di indagare sui veri fallimenti.
  • Tempo sprecato. Gli sviluppatori trascorrono ore a rieseguire pipeline ed esaminare errori che non hanno nulla a che fare con la modifica.
  • Consegna più lenta. Le unioni e i rilasci attendono le repliche, minando il punto di CI/CD.
  • Bug nascosti. Le condizioni di gara nel prodotto possono assomigliare al rumore del test e sono quelle che danneggiano la produzione.

Le cause più comuni in breve

Causa Segno tipico Solita correzione
Timing e asincrono Fallisce di più quando CI è occupato Attendi una condizione
Stato condiviso Fallisce a seconda dell’ordine del test Isola i dati, ripristina lo stato
Date e casualità Fallisce in determinati orari o giorni Controlla l’orologio e il seme
Selettori fragili Fallisce dopo le modifiche all’interfaccia utente Utilizzare localizzatori stabili
Servizi esterni Fallisce quando una dipendenza è lenta Derideteli o storpiateli
Differenze ambientali Passa localmente, fallisce in CI Allineare con i contenitori
Limiti delle risorse Fallisce durante le esecuzioni parallele Sintonizza i lavoratori, risolvi i conflitti

Causa 1: tempistica e sonni fissi

Questa è la causa più comune. Un test presuppone che il lavoro in background verrà completato dopo un ritardo fisso. Per vedere l’effetto, abbiamo scritto un piccolo test in cui il lavoro dura fino a 2 ms, mentre il test attende solo 1 ms prima di verificare:

def flaky_timing():
    result = {}
    def work():
        time.sleep(random.uniform(0, 0.002))
        result["done"] = True
    threading.Thread(target=work).start()
    time.sleep(0.001)        # hope it has finished
    assert result.get("done")

La soluzione è attendere le condizioni effettive, con un limite superiore generoso:

def fixed_timing():
    result = threading.Event()
    def work():
        time.sleep(random.uniform(0, 0.002))
        result.set()
    threading.Thread(target=work).start()
    assert result.wait(timeout=1)   # continues as soon as it is ready

Abbiamo eseguito ciascuna versione 200 volte:

Versione Fallimenti su 200
Sonno fisso 102
Attendi una condizione 0

Il risultato è molto più che semplice affidabilità. La versione fissa si sposta nel momento in cui il lavoro viene terminato, quindi di solito è anche più veloce. Una sospensione più lunga farebbe sì che la prima versione fallisca meno spesso, ma ogni esecuzione sarebbe quindi più lenta e potrebbe comunque fallire su una macchina CI occupata.

Nei test del browser si applica lo stesso principio. Utilizza l’attesa automatica di Drammaturgo, o le Attese di selenio esplicite, invece di time.sleep:

# Playwright: retries until true or a timeout is reached
expect(page.get_by_text("Order placed")).to_be_visible()

# Selenium: an explicit wait for a condition
WebDriverWait(driver, 10).until(
    EC.visibility_of_element_located((By.ID, "confirmation"))
)

Causa 2: stato condiviso e ordine di test

Quando i test condividono dati, uno può influenzarne un altro e il risultato dipende dall’ordine in cui vengono eseguiti. Questi due test utilizzano un elenco condiviso:

store = []

def test_add():
    store.append("x")
    assert len(store) == 1

def test_empty():
    assert store == []
Ordine test_add test_empty
vuoto, quindi aggiungere Passa Passa
aggiungi, quindi svuota Passa Fallire

La nostra esecuzione lo ha confermato, ed è per questo che una suite può durare mesi e poi rompersi quando qualcuno aggiunge esecuzioni parallele o un plug-in per l’ordine casuale. La cura è l’isolamento:

  • Fornisci a ogni test dati aggiornati, utilizzando un dispositivo che lo imposta e lo pulisce. Il nostro tutorial pytest mostra come.
  • Crea record univoci, come un’e-mail con un suffisso casuale, al posto di un “utente di prova” condiviso.
  • Avvolgi i test del database in una transazione ed esegui il rollback, laddove la tua configurazione lo consente.
  • Esegui regolarmente i test in ordine casuale, in modo che questi problemi si manifestino presto.

Causa 3: date, fusi orari e casualità

Un test che supera tutta la settimana può fallire il sabato, a mezzanotte o l’ultimo giorno del mese. Rendi il tempo un input, invece di leggere l’orologio all’interno della tua logica:

def is_weekend(today=None):
    today = today or datetime.date.today()
    return today.weekday() >= 5

is_weekend(datetime.date(2026, 11, 7))   # True  (a Saturday)
is_weekend(datetime.date(2026, 11, 9))   # False (a Monday)

Questi test danno la stessa risposta ogni giorno. Allo stesso modo, imposta un seed fisso per valori casuali nei test e registralo quando utilizzi dati casuali in modo da poter riprodurre un errore. Non assumere un ordine particolare da insiemi, dizionari da fonti esterne o query di database senza ORDER BY.

Causa 4: Selettori fragili

Un test che trova un pulsante con div:nth-child(3) > span si interrompe con qualsiasi modifica al layout e potrebbe fallire solo per alcune dimensioni o varianti dello schermo. Preferire i localizzatori in base a ciò che vedono gli utenti o attributi di test dedicati. La nostra guida ai Locatori di drammaturghi spiega l’ordine in cui provarli. Le animazioni sono un’altra frequente fonte di problemi, quindi considera la possibilità di disabilitarle negli ambienti di test.

Causa 5: servizi e reti esterni

Se un test chiama un vero fornitore di servizi di pagamento o un’API di terze parti, il risultato dipende dai tempi di attività e dai limiti di velocità di qualcun altro. Prendi in giro quelle chiamate per la maggior parte dei test e mantieni un piccolo numero di controlli contrattuali separati rispetto al servizio reale, in modo da sapere comunque se cambia. La nostra guida ai test unitari e di integrazione spiega come suddividerli.

Suggerimento: “Passa localmente, fallisce in CI” di solito significa una differenza di velocità, fuso orario, risorse o versioni. L’esecuzione di test nella stessa immagine del contenitore localmente e in CI ne rimuove molti. Il nostro tutorial su Docker Compose può aiutarti.

Come diagnosticare un test traballante

  1. Confermalo. Esegui il test più volte senza apportare modifiche. Con pytest, funziona un semplice ciclo di shell e un plugin come pytest-repeat ti dà --count=100.
  2. Registra prove. Salva registri, screenshot e tracce per ogni errore. Il visualizzatore di tracce di Playwright è particolarmente utile qui.
  3. Sottolinea le condizioni. Eseguilo con CPU limitata, in parallelo o in ordine casuale. Far fallire più spesso rende più facile studiare.
  4. Testalo da solo. Se passa sempre da solo ma fallisce nella suite, sospetta lo stato condiviso.
  5. Cerca modelli. Controlla quali macchine, orari del giorno e test vicini sono coinvolti.
  6. Controlla il prodotto. Chiedi onestamente se questa potrebbe essere una vera e propria condizione di gara nella tua applicazione.

I tentativi automatici sono una soluzione?

I nuovi tentativi sono un’utile rete di sicurezza a breve termine e non sono una cura. Un singolo tentativo automatico in CI può continuare a lavorare mentre indaghi, ma un test che necessita di un nuovo tentativo per essere superato ti dice comunque qualcosa. Peggio ancora, un vero bug intermittente del prodotto che fallisce il 20% delle volte di solito passa inosservato dopo un nuovo tentativo.

Usa i tentativi con queste regole: limitali a uno o due, registra ogni test che ne richiedeva uno e tratta quell’elenco come la tua lista di cose da fare. Un test segnalato come “superato al nuovo tentativo” dovrebbe essere considerato un problema su cui indagare, non un successo.

Un processo di squadra per test instabili

  1. Misura. Tieni traccia dei test falliti e poi passa lo stesso commit. Molti strumenti CI e reporter di test possono dimostrarlo.
  2. Quarantena. Sposta un test instabile confermato fuori dall’esecuzione del blocco, in modo che smetta di disturbare tutti, ma continua a eseguirlo e a monitorarlo.
  3. Assegna un proprietario e una scadenza. Una quarantena senza scadenza diventa semplicemente un cimitero.
  4. Correggi o elimina. Se non vale la pena correggere un test, rimuovilo e controlla che la sua copertura esista ancora altrove.
  5. Prevenire. Esegui nuovi test più volte prima di unirli. Un test che fallisce una volta su cinquanta viene eseguito meglio adesso che tra sei mesi.

Tratta un test instabile come un bug con priorità e non come rumore di fondo. Anche qui si applicano gravità e priorità, come spiegato in gravità e priorità. Se l’instabilità è diffusa, leggi gli errori di automazione dei test più ampi e valuta se hai troppi test a livello di browser.

Considerazioni finali

I test instabili sembrano casuali, ma raramente lo sono. Quasi tutti si basano su un presupposto sul tempo, sull’ordine, sui dati o sull’ambiente che non è sempre vero. Scegli il test che fallisce più spesso nella tua pipeline questa settimana. Eseguilo cinquanta volte, trova il presupposto e risolvilo per sempre. Una vera soluzione insegna a una squadra che più di un centinaio di repliche potranno mai farlo.

Domande frequenti

Cos’è un test traballante?

Un test instabile supera alcune esecuzioni e fallisce in altre senza alcuna modifica nel codice. La causa potrebbe risiedere nel test, nell’ambiente o nel prodotto.

Quali sono le cause dei test instabili?

Le cause più comuni sono i presupposti temporali, lo stato condiviso tra test, selettori instabili, servizi esterni, gestione delle date e differenze tra CI e macchine locali.

Devo eliminare i test instabili?

Per prima cosa prova a risolverli. Se un test ha un valore basso ed è costoso da correggere, eliminarlo va bene, ma assicurati che la sua copertura importante esista da qualche altra parte.

Devo riprovare automaticamente i test falliti?

Come rete di sicurezza limitata, sì, ma registra ogni tentativo e indaga. I soli tentativi nascondono i problemi e possono consentire a veri bug intermittenti di raggiungere la produzione.

Come trovo i test instabili?

Cerca i test che falliscono e passano lo stesso commit, utilizzando la cronologia dell’elemento della configurazione o uno strumento di reporting. È inoltre possibile eseguire i test sospetti più volte in un ciclo.

Continua a imparare

Lavora con Stack Quora

Hai bisogno di aiuto con test, WordPress o SEO?

Aiutiamo i team a fornire software migliore e a far crescere i loro siti. Raccontaci a cosa stai lavorando e ti suggeriremo chiaramente il prossimo passo.

Continua a leggere

Altre guide su test, intelligenza artificiale e sviluppo moderno

Ti è piaciuto questo articolo? Scegli un argomento e passa direttamente ai nostri tutorial più recenti.

Intelligenza artificiale

Spiegazione delle allucinazioni dell'intelligenza artificiale: perché i modelli linguistici inventano le cose e come ridurle

Spiegazione delle allucinazioni dell'intelligenza artificiale: cosa sono, perché i modelli linguistici le producono, tipi comuni, come ridurle con b…

Leggi l'articolo →
DevOps

Spiegazione delle variabili d'ambiente: file .env, segreti e configurazione eseguiti correttamente

Variabili d'ambiente spiegate con esempi Python che abbiamo eseguito: lettura e impostazione, perché ogni valore è una stringa, file .env, ereditari…

Leggi l'articolo →
Sviluppo software

Spiegazione della limitazione della velocità API: algoritmi, intestazioni e gestione degli errori 429

La limitazione della velocità dell'API spiegata con una simulazione che abbiamo eseguito: bucket di token, finestra fissa, finestra scorrevole, risp…

Leggi l'articolo →
Automazione dei test

Derisione in Python: unittest.mock, patch e gli errori che fanno mentire i test

Mocking in Python con unittest.mock, testato: Mock e return_value, side_effect, patch dove viene utilizzato un nome, specifiche e autospec, date moc…

Leggi l'articolo →
Test del software

Spiegazione della copertura del codice: copertura di linea o di filiale e cosa significa realmente il 100%.

Copertura del codice spiegata con una vera esecuzione di cover.py: copertura di linea, ramo e condizione, lettura di un rapporto, perché la copertur…

Leggi l'articolo →
Database e SQL

DELETE vs TRUNCATE vs DROP in SQL: differenze, rollback e quando utilizzarli

DELETE vs TRUNCATE vs DROP spiegato con SQL testato: cosa rimuove, rollback, trigger, reimpostazione dell'identità, velocità e come differiscono Pos…

Leggi l'articolo →
Database e SQL

Spiegazione delle visualizzazioni SQL: crea, utilizza ed evita le insidie, con esempi

Viste SQL spiegate con le query che abbiamo eseguito: creazione di una vista, risultati in tempo reale, nascondere colonne, prestazioni, viste aggio…

Leggi l'articolo →
Sviluppo software

Interfaccia TypeScript e tipo: differenze reali, testate con il compilatore

Interfaccia TypeScript vs tipo, verificata con il vero compilatore: cosa possono fare entrambi, cosa può fare solo uno, fusione delle dichiarazioni,…

Leggi l'articolo →
Sviluppo software

Spiegazione di Python *args e **kwargs: funzioni flessibili con esempi chiari

Python *args e **kwargs spiegati con il codice che abbiamo eseguito: compressione e decompressione, ordine dei parametri, argomenti solo per parole …

Leggi l'articolo →
Sviluppo software

Funzioni Lambda Python: sintassi, usi reali e quando non usarli

Funzioni lambda Python spiegate con il codice che abbiamo eseguito: sintassi, ordinamento con chiave, mappa e filtro, chiusure, trap dell'associazio…

Leggi l'articolo →
Intelligenza artificiale

Cosa sono gli agenti AI? Come funzionano, con un semplice ciclo di agenti che puoi eseguire

Cosa sono gli agenti IA? Una semplice guida al ciclo dell'agente, agli strumenti, alla memoria e ai guardrail, con un piccolo ciclo Python che abbia…

Leggi l'articolo →
DevOps

Monitoraggio vs osservabilità: la vera differenza, con log, metriche e tracce

Spiegazione del monitoraggio e dell'osservabilità: errori noti e sconosciuti, log, metriche e tracce, una procedura dettagliata sugli incidenti di p…

Leggi l'articolo →
DevOps

Volumi Docker e montaggi di associazione: mantenere i dati del contenitore nel modo giusto

Volumi Docker e montaggi di collegamento spiegati: perché i dati del contenitore scompaiono, volumi denominati, montaggi di collegamento, tmpfs, ese…

Leggi l'articolo →
Automazione dei test

TDD vs BDD: differenze chiave, una caratteristica costruita in entrambi i modi e quando utilizzarli ciascuno

TDD vs BDD spiegato con una funzione di sconto costruita in entrambi i modi: refactoring rosso-verde con il codice che abbiamo eseguito, scenari "Da…

Leggi l'articolo →
Test del software

Test di accettazione da parte dell'utente (UAT): processo, lista di controllo ed esempi reali

Spiegazione del test di accettazione dell'utente: chi lo esegue, in cosa differisce dal QA, un processo in sette fasi, un modello di test case UAT, …

Leggi l'articolo →
Database e SQL

Spiegazione dei valori SQL NULL: IS NULL, COALESCE, NULLIF e le trappole da evitare

Valori SQL NULL spiegati con le query che abbiamo eseguito: logica a tre valori, IS NULL, COALESCE, NULLIF, come aggregazioni, join e ordinamento tr…

Leggi l'articolo →
Database e SQL

Istruzione SQL CASE WHEN: sintassi, esempi reali ed errori comuni

SQL CASE WHEN spiegato con le query che abbiamo eseguito: moduli semplici e cercati, ordine delle condizioni, pivot con SUM, trucchi COUNT, ordiname…

Leggi l'articolo →
Sviluppo software

Cos'è il JWT? Token Web JSON spiegati con un esempio funzionante

Cos'è il JWT? Spiegazione dei token Web JSON: le tre parti, un token creato e manomesso nel codice che abbiamo eseguito, JWT e sessioni, archiviazio…

Leggi l'articolo →
Sviluppo software

Spiegazione del loop di eventi JavaScript: stack di chiamate, microtask e timer con esempi

Il ciclo di eventi JavaScript spiegato con il codice che abbiamo eseguito: stack di chiamate, code di attività e microtask, perché setTimeout 0 non …

Leggi l'articolo →
Sviluppo software

Spiegazione dei generatori Python e del rendimento: risparmia memoria con esempi reali

Generatori Python e rendimento spiegati con il codice che abbiamo eseguito: come vengono messi in pausa e ripresi, misurato il risparmio di memoria,…

Leggi l'articolo →
Intelligenza artificiale

Cosa sono gli incorporamenti? I vettori e la somiglianza del coseno spiegati semplicemente

Cosa sono gli incorporamenti? Una guida in linguaggio semplice ai vettori, alla similarità del coseno e alla ricerca semantica, con un piccolo esemp…

Leggi l'articolo →
DevOps

Distribuzione blu-verde rispetto a quella canaria: differenze, compromessi e come scegliere

Spiegazione della distribuzione blu-verde e canary: come funziona ciascuna, costi, rischi e rollback, bucket utente stabile che abbiamo testato, mig…

Leggi l'articolo →
Automazione dei test

Test instabili: quali sono le cause e come risolverli definitivamente

Test instabili spiegati con demo che abbiamo eseguito: le cause principali, come confermarne e diagnosticarne uno, soluzioni adeguate per tempistich…

Leggi l'articolo →
Test del software

Test di carico e test di stress: differenze, metriche ed esempi k6

Spiegazione del test di carico e dello stress test: cosa dimostra ciascuno, test di picco e di assorbimento, le metriche che contano, perché le medi…

Leggi l'articolo →
Test del software

Caso di test e scenario di test: differenze chiave, esempi e quando utilizzarli

Spiegazione del caso di test e dello scenario di test: definizioni chiare, una tabella affiancata, una funzione di reimpostazione della password pre…

Leggi l'articolo →
Database e SQL

Ottimizzazione delle query SQL: leggi i piani di esecuzione e correggi query lente, con tempistiche

Ottimizzazione delle query SQL testata su un milione di righe: leggi i piani di esecuzione, aggiungi gli indici corretti, correggi filtri non aggior…

Leggi l'articolo →
Database e SQL

SQL UNION vs UNION ALL: differenze, esempi e prestazioni testate

SQL UNION vs UNION ALL spiegati con le query che abbiamo eseguito: duplicati, regole seguite da entrambi, un vero bug di dati, prestazioni misurate,…

Leggi l'articolo →
Sviluppo software

Tutorial sull'API di recupero JavaScript: GET, POST, gestione degli errori e timeout

Un tutorial sull'API JavaScript Fetch testato su un server locale: GET e POST, perché 404 non genera eccezioni, un helper riutilizzabile, stringhe d…

Leggi l'articolo →
Sviluppo software

Gestione dei file Python: lettura, scrittura, CSV, JSON e pathlib con esempi

Gestione dei file Python spiegata con il codice che abbiamo eseguito: modalità aperte, con, lettura di file di grandi dimensioni, CSV, JSON, pathlib…

Leggi l'articolo →
Sviluppo software

Git merge vs rebase: differenze, esempi e quando usare ciascuno

Git merge vs rebase spiegato con output reale del log, una tabella di confronto, la regola d'oro del rebase e un flusso di lavoro sicuro per i team.

Leggi l'articolo →
Sviluppo software

List comprehension in Python: sintassi, esempi e quando evitarla

List comprehension in Python spiegata: sintassi, filtri, if-else, cicli annidati, tempi misurati e i casi in cui un normale ciclo for è meglio.

Leggi l'articolo →
Database e SQL

SQL JOIN spiegati con esempi (Inner, Left, Right, Full)

Capisci i JOIN SQL passo dopo passo con tabelle e query di esempio: INNER, LEFT, RIGHT, FULL, CROSS e SELF, con errori comuni e consigli sulle prest…

Leggi l'articolo →