Qual è la differenza tra un test unitario e un test di integrazione?
Un test unitario isola un piccolo frammento di codice, come una classe o una funzione, e ne verifica il comportamento senza toccare sistemi esterni, usando fake o mock per i collaboratori. Un test di integrazione esercita più componenti insieme, spesso con infrastruttura reale come un database o un message broker, per confermare che collaborino correttamente. I test unitari sono veloci e numerosi e formano la base della piramide dei test; quelli di integrazione sono meno numerosi e più lenti ma colgono difetti di cablaggio, mapping, SQL e serializzazione che i test unitari con mock non possono.
Cos'è la piramide dei test e perché conta?
La piramide dei test è un'euristica per la forma di una suite sana: molti test unitari veloci alla base, meno test di integrazione nel mezzo e pochissimi test end-to-end lenti in cima. Conta perché spinge ogni difetto al livello più economico in grado di coglierlo, mantenendo il feedback rapido e la suite affidabile. L'anti-pattern è il "cono gelato" - soprattutto test end-to-end - che produce suite lente e instabili che i team alla fine smettono di fidarsi e di eseguire.
A quale code coverage dovrei puntare?
Non esiste un numero universalmente corretto; la copertura è un indicatore di completezza, non una misura della qualità dei test. Una cifra intorno all'80% è una soglia comune e pragmatica che segnala ampie aree non testate, ma il 100% di copertura può comunque tralasciare comportamenti, casi limite e percorsi di errore se i test non affermano esiti significativi. Usa la copertura per individuare codice non testato che merita test e abbinala alla review (e idealmente al mutation testing) che verifica che i test affermino davvero il comportamento anziché eseguire solo righe.
Qual è la differenza tra un mock, uno stub e un fake?
Tutti e tre sono test double che sostituiscono collaboratori reali, ma servono scopi diversi. Uno stub restituisce risposte predefinite per fornire input indiretti e non verifica le chiamate; un fake ha un'implementazione funzionante ma semplificata, come un database in memoria; un mock è pre-programmato con aspettative e può far fallire il test se le interazioni attese non si verificano. Usa stub e fake per i test basati sullo stato e i mock con parsimonia per la verifica delle interazioni, perché l'eccesso di mock accoppia i test ai dettagli di implementazione e fa passare test su codice rotto.
Come correggo un test instabile?
Prima rendi riproducibile il fallimento individuando la fonte del non determinismo: condizioni di timing e di race, dipendenza dall'orologio, stato mutabile condiviso, dipendenza dall'ordine dei test o dipendenze esterne instabili. Poi rimuovi la causa - inietta un orologio fisso anziché leggere l'ora corrente, attendi una condizione osservabile invece di uno sleep di durata fissa, isola o reimposta lo stato per test e sostituisci i servizi esterni con stub. Evita la tentazione di mascherare l'instabilità con ritentativi alla cieca, che nascondono bug intermittenti reali ed erodono lentamente la fiducia nell'intera suite.
Quando dovrei scrivere un test end-to-end invece di uno unitario?
Riserva i test end-to-end a un numero ridotto di percorsi utente critici dove la fiducia di esercitare il sistema interamente integrato giustifica la loro lentezza e fragilità, come login, checkout o flussi di dati centrali. Spingi tutto il resto verso il basso nella piramide: testa le regole di business e i casi limite come test unitari, e il cablaggio, la persistenza e i contratti come test di integrazione. Un difetto che un test unitario o di integrazione può cogliere va colto lì, perché quei test sono più veloci, più stabili e individuano il fallimento con più precisione.
Cos'è il test-driven development e quali sono i suoi benefici?
Il test-driven development è un ciclo in cui si scrive prima un test che fallisce (red), si scrive il codice minimo per farlo passare (green) e poi si refattorizza con il test come rete di sicurezza (refactor). Il suo beneficio principale è il design: scrivere prima il test costringe a definire l'interfaccia e il comportamento dell'unità dal punto di vista del chiamante, il che tende a produrre codice piccolo, disaccoppiato e testabile. Come sottoprodotto si accumula una suite di regressione veloce e si ottiene feedback immediato, anche se la disciplina richiede pratica ed è più utile dove il comportamento è ben compreso.
Come dovrei testare codice che dipende dalla data o dall'ora correnti?
Non leggere mai l'orologio di sistema direttamente nel codice sotto test, perché ciò rende il comportamento non deterministico e i test dipendenti dal tempo instabili. Inietta invece un'astrazione per il tempo - come un Clock in Java, un'interfaccia di clock o una funzione che restituisce "adesso" - così che un test possa fornire un istante fisso. Questo permette di verificare il comportamento a momenti esatti (confini di scadenza, finestre di scheduling, transizioni di fuso orario) in modo deterministico e di eseguire lo stesso test in qualsiasi giorno senza sorprese.
Qual è la differenza tra QA e testing?
Il testing è l'attività di eseguire il software per trovare difetti e verificare il comportamento; la quality assurance è la più ampia disciplina orientata al processo di costruire la qualità nel modo in cui il software viene prodotto. La QA abbraccia chiarezza dei requisiti, code review, strategia di test, gestione degli ambienti e miglioramento continuo, di cui il testing è una parte importante. Riconfigurare la qualità come responsabilità dell'intero team - anziché una fase di un gruppo separato alla fine - è una caratteristica distintiva della delivery moderna in stile agile e DevOps.
Come testo un sistema che chiama API esterne?
Per test unitari rapidi e deterministici, sostituisci la chiamata esterna con uno stub o un fake che restituisce risposte controllate, inclusi i casi di errore e timeout. Per i test di integrazione, preferisci un sostituto realistico al terzo reale: un contract test (per esempio con Pact) per verificare la forma concordata di richiesta/risposta, o un server mock locale come WireMock o MSW per simulare l'API inclusi i guasti. Riserva l'accesso al servizio reale a un numero ridotto di controlli end-to-end o smoke monitorati, perché le dipendenze reali rendono le suite lente, instabili e dipendenti dalla disponibilità esterna.
Cos'è il mutation testing e vale il costo?
Il mutation testing valuta quanto sono buoni i tuoi test introducendo piccoli guasti ("mutanti") nel codice di produzione e verificando se i test li catturano; un mutante sopravvissuto segna un comportamento che i test non verificano davvero. È un segnale di qualità molto più forte della copertura delle righe, che prova solo che il codice è stato eseguito, non che il suo comportamento sia stato affermato. Il compromesso è il tempo di esecuzione, perciò di solito conviene applicarlo a moduli critici o complessi anziché all'intera codebase a ogni commit.
Come faccio a rendere più veloce la mia suite di test?
Parti dalla forma corretta della suite: la maggior parte dei difetti dovrebbe essere colta da test unitari veloci e isolati, con test di integrazione ed end-to-end mantenuti proporzionalmente pochi. Rendi i test indipendenti così da poterli eseguire in parallelo, evita I/O e sleep non necessari, condividi in sicurezza il setup costoso (per esempio riusando un solo database containerizzato in una classe) e separa le suite lente così che i controlli rapidi girino a ogni commit e quelli lenti più di rado. Profila la suite per trovare i test più lenti, perché di solito un numero ristretto di essi domina il tempo totale.
Qual è la differenza tra load testing e stress testing?
Un load test misura come il sistema si comporta sotto un livello atteso o di picco di domanda concorrente e verifica che latenza, throughput e tasso di errore rispettino i loro obiettivi - rispondendo a "è abbastanza veloce e affidabile a questo volume?". Uno stress test spinge deliberatamente oltre la capacità attesa per trovare il punto di rottura e osservare come il sistema degrada e si riprende. Usa i load test per validare le performance rispetto alle SLO e gli stress test per comprendere le modalità di guasto e confermare un degrado graduale.
Dovrei eliminare i test lenti o fastidiosi?
Non eliminare un test che protegge un comportamento reale solo perché è scomodo; piuttosto diagnostica perché è lento o rumoroso e correggi il problema sottostante. Se un test è instabile, rendilo deterministico; se è lento, sposta l'asserzione a un livello più veloce o ottimizza il suo setup; se è ridondante rispetto a test più forti, puoi rimuoverlo deliberatamente annotando il perché. Disabilitare o eliminare silenziosamente i test rimuove la sicurezza senza che nessuno decida che il rischio è accettabile, ed è così che le regressioni rientrano.
Come valuto la qualità di una funzionalità di IA o LLM?
Trattalo come un problema di misurazione: costruisci un set di valutazione di input rappresentativi con risposte di riferimento o proprietà, e assegna punteggi agli output con metriche automatiche più una revisione umana mirata. Per la retrieval-augmented generation, framework come RAGAS, promptfoo e DeepEval misurano la faithfulness (la risposta è ancorata al contesto recuperato?), l'answer relevancy e il context recall/precision. Metti un gate ai rilasci su queste metriche in CI così che un cambiamento di prompt, modello o retrieval non possa regredire silenziosamente la qualità, e mantieni fisso il golden set così da misurare il sistema, non modificare l'obiettivo.
Cosa sono i criteri di accettazione e come si legano ai test di accettazione?
I criteri di accettazione sono le condizioni predefinite e testabili che una funzionalità deve soddisfare per essere accettata dagli stakeholder, idealmente scritte prima o durante lo sviluppo così che il "fatto" sia oggettivo. I test di accettazione sono i controlli eseguibili che verificano quei criteri dal punto di vista dell'utente, spesso formulati come scenari Given/When/Then. Concordare i criteri in anticipo - il cuore dell'acceptance test-driven development e della specification by example - riduce il rilavoro causato da requisiti ambigui e dà al team un obiettivo condiviso e verificabile.
Cosa rende buono un bug report?
Un buon bug report è riproducibile e specifico: indica i passi esatti per innescare il problema, il risultato atteso, il risultato effettivo e l'ambiente (versione, OS, browser, dati). Include il caso minimo che dimostra il problema, log o screenshot rilevanti e la gravità e l'impatto così che il team possa stabilire le priorità. L'elemento più prezioso in assoluto è una riproduzione affidabile, perché un difetto che non si può riprodurre di solito non si può correggere con sicurezza - e idealmente viene catturato come test di regressione fallito.