Premature optimization is the root of all evil.

Testing 🧪

Testing 🧪

I test sono come le fondamenta di una casa: nessuno li vede, ma senza di loro tutto crolla. Non sono solo un modo per verificare che il codice funzioni, ma sono anche documentazione viva, rete di sicurezza per i refactoring e, soprattutto, il miglior antidoto contro le chiamate di emergenza nel weekend. In questa guida vedremo quali sono le opzioni per costruire una strategia di testing efficace, dalle fondamenta (unit test) fino al tetto (end-to-end), passando per tutti quei test che fanno la differenza tra “sembra funzionare” e “funziona davvero”.

Test pyramid: la piramide della verità (o quasi) 🏛️

Se pensavi che la piramide servisse solo agli egizi, sappi che anche nel software la “Test Pyramid” è un monumento sacro. In basso tanti test veloci e affidabili (unit), in alto pochi test lenti e costosi (end-to-end). Più sali, più i test diventano lenti, fragili e costosi da mantenere. E se pensi che basti fare solo unit test, preparati a scoprire bug che si nascondono meglio di un developer il venerdì pomeriggio.

  • Unit: tanti, veloci, spietati
  • Integration: meno, ma fondamentali
  • Functional/E2E: pochi, ma senza di loro rischi di scoprire che il bottone “Paga” non paga

Tipi di test 🧩

Unit test: il fitness quotidiano del codice 💪

Gli unit test sono la palestra del codice: piccoli, veloci, isolati. Ma attenzione: se ti limiti a testare solo i casi “happy path”, rischi di avere muscoli solo sulle braccia (e bug ovunque altrove). Ricorda: mock e stub sono i tuoi amici, ma non esagerare o rischi di testare solo le tue illusioni. Gli unit test dovrebbero essere tanti, coprire casi normali e limiti, e soprattutto essere indipendenti tra loro. Se uno fallisce, non deve crollare tutto come un castello di carte.

Integration test: quando il codice fa amicizia 🤝

Qui si testano le interazioni tra moduli. Esistono vari approcci:

  • Big-bang: testare tutto insieme (spoiler: spesso esplode).
  • Top-down: parti dall’alto e scendi (ottimo per architetture a cipolla).
  • Bottom-up: dal basso verso l’alto (più facile all’inizio, ma rischi di perderti per strada).
  • Sandwich/Hybrid: un po’ di tutto, come la pizza con l’ananas (non per tutti i gusti).

Gli integration test sono più lenti e complessi, ma fondamentali per evitare che i tuoi microservizi si ignorino come colleghi in smart working. Difficili da mantenere, ma se ben progettati ti salvano da bug che solo l’interazione tra componenti può generare.

Functional test: la realtà simulata 🎭

Qui si verifica che il software faccia davvero quello che promette. Si testano i requisiti funzionali, spesso tramite API o interfacce utente. Non confonderli con i test di performance o di sicurezza: qui si guarda solo se “fa quello che deve”, non se lo fa in 2ms o senza farsi bucare da un hacker annoiato. Sono spesso end-to-end e simulano l’utente vero, quindi sono lenti e fragili, ma indispensabili per dormire sonni tranquilli.

End-to-end (E2E) test: il viaggio completo 🚦

Gli E2E test sono il collaudo generale: simulano l’intero percorso dell’utente, dal login al checkout, attraversando tutti i passaggi critici e integrando ogni componente del sistema. L’obiettivo è verificare che tutte le parti – frontend, backend, database, servizi esterni – collaborino come previsto. Questi test sono fondamentali per scoprire problemi che emergono solo quando tutto gira insieme, come errori di integrazione, problemi di sessione o flussi utente interrotti.

Quando usarli?

  • Per validare i flussi principali (acquisto, registrazione, reset password)
  • Per testare l’integrazione tra frontend, backend e database
  • Per simulare scenari reali e garantire che l’utente finale non incontri sorprese

Attenzione: troppi E2E possono rallentare la pipeline e complicare la manutenzione. Scegli con cura i flussi davvero critici da coprire.

Regression test: il guardiano del passato 🕰️

I regression test servono a garantire che le nuove modifiche non rompano ciò che già funzionava. Ogni volta che correggi un bug o aggiungi una feature, aggiungi anche un regression test: il futuro te stesso (e il team) ti ringrazierà. Questi test sono la memoria storica del progetto: aiutano a evitare che vecchi problemi si ripresentino dopo una modifica, anche mesi dopo.

Quando servono?

  • Dopo ogni fix di bug
  • Dopo refactoring importanti
  • Quando una funzionalità è particolarmente delicata o soggetta a regressioni

Una buona suite di regression test è la migliore assicurazione contro i “bug zombie” che tornano quando meno te lo aspetti.

Performance test: la prova del fuoco 🔥

I performance test misurano quanto il sistema regge sotto stress: tempi di risposta, throughput, scalabilità. Meglio scoprire i limiti in laboratorio che durante il Black Friday. Questi test aiutano a individuare colli di bottiglia, memory leak e problemi di scalabilità prima che diventino emergenze.

Cosa misurare?

  • Tempo di risposta delle API e delle pagine web
  • Capacità di gestire carichi elevati e picchi di traffico
  • Scalabilità orizzontale e verticale
  • Stabilità nel tempo sotto carico prolungato

Automatizza i performance test e schedulali regolarmente: le performance cambiano anche con piccole modifiche al codice o all’infrastruttura.

Security test: la cassaforte del software 🔒

I security test cercano vulnerabilità, injection, XSS e altre minacce. Non sono solo per le banche: anche il tuo blog merita di non essere bucato dal primo script kiddie di passaggio. Questi test sono fondamentali per proteggere dati sensibili, garantire la privacy degli utenti e rispettare normative come GDPR.

Cosa testare?

  • Input validation e sanitizzazione
  • Autorizzazioni e autenticazione
  • Protezione da SQL injection, XSS, CSRF e altre vulnerabilità note
  • Sicurezza delle API e delle comunicazioni tra servizi

Strumenti come gosec possono aiutarti a trovare vulnerabilità nel codice Go. Non trascurare i security test: una piccola falla può avere grandi conseguenze.

Usability test: il giudizio degli umani 👀

Questi test valutano l’esperienza utente e l’accessibilità. Se il bottone “Compra” è invisibile o il font poco leggibile, meglio saperlo prima che dopo il rilascio. Gli usability test aiutano a capire se l’interfaccia è intuitiva, se i flussi sono chiari e se il prodotto è accessibile anche a chi ha disabilità.

Quando farli?

  • Prima di rilasciare nuove UI o funzionalità
  • Dopo feedback negativi dagli utenti
  • Quando vuoi migliorare la soddisfazione e la fidelizzazione degli utenti

Qui serve l’occhio umano: chiedi a un collega, a un utente reale o a chiunque non abbia scritto il codice. L’usabilità non si improvvisa: testare con persone reali fa la differenza. Tuttavia, esistono anche strumenti automatici per valutare l’accessibilità e l’usabilità, come axe, Lighthouse o WAVE: usali per una prima analisi, ma non sostituiscono il giudizio umano.

Acceptance test: il cliente ha sempre ragione ✅

Gli acceptance test verificano che il software soddisfi i requisiti del cliente o dello stakeholder. Se passano, puoi festeggiare (ma non troppo: i requisiti cambiano spesso). Questi test sono spesso scritti in collaborazione con il cliente, rappresentano la “definizione di fatto” di cosa significa “funziona” e, dove possibile, vengono automatizzati per garantire coerenza nel tempo.

Come si scrivono?

  • Insieme al cliente o al product owner, per chiarire aspettative e criteri di accettazione
  • In linguaggio naturale o con strumenti come Gherkin, per essere comprensibili a tutti
  • Automatizzati dove possibile, per garantire coerenza nel tempo

Gli acceptance test sono la base per evitare incomprensioni e discussioni infinite su cosa sia “finito”.

Compatibility test: il mondo è vario 🌍

Questi test controllano che l’app funzioni su diversi dispositivi, browser e sistemi operativi. Perché non tutti usano il tuo browser preferito (e qualcuno usa ancora Internet Explorer… incredibile ma vero). I compatibility test sono fondamentali per prodotti consumer, SaaS e app mobile.

Quando servono?

  • Prima di ogni rilascio importante
  • Se hai utenti su piattaforme diverse o in mercati internazionali
  • Quando introduci nuove dipendenze o tecnologie frontend

Automatizza con strumenti di cross-browser testing o chiedi ai colleghi con device diversi. La compatibilità è spesso sottovalutata… finché non arrivano le segnalazioni dagli utenti.

Configuration test: la prova delle impostazioni ⚙️

I configuration test verificano che l’applicazione si comporti correttamente con diverse configurazioni: variabili d’ambiente, feature flag, file di settings, parametri runtime. Sono fondamentali per evitare che un semplice errore di configurazione mandi tutto in tilt, soprattutto in ambienti cloud, container o microservizi.

Quando servono?

  • Quando l’app gira in ambienti diversi (dev, staging, prod)
  • Se usi feature flag o parametri dinamici
  • Dopo modifiche a file di configurazione o variabili d’ambiente

Automatizza questi test per evitare sorprese dopo il deploy. Una configurazione sbagliata può causare più danni di un bug nel codice!

Chaos testing: la resilienza sotto stress 🧨

Il chaos testing (o chaos engineering) consiste nel simulare guasti, disservizi o condizioni avverse (es. crash di servizi, perdita di rete, latenza elevata) per verificare la resilienza del sistema. L’obiettivo è scoprire punti deboli e migliorare la robustezza prima che i problemi si presentino in produzione.

Quando usarlo?

  • In architetture distribuite, cloud o microservizi
  • Per validare strategie di failover, auto-scaling e recovery
  • Periodicamente, come parte della cultura DevOps

Strumenti come Chaos Mesh, Gremlin o semplici script custom possono aiutarti a introdurre “caos controllato”. Meglio scoprire i limiti in laboratorio che durante un incidente reale!

Exploratory test: l’arte dell’improvvisazione 🕵️

Qui il tester segue l’istinto, esplorando il software senza uno script preciso. Perfetti per scoprire bug che nessun test automatico troverà mai. Gli exploratory test sono utili soprattutto in fase di prototipazione o quando si lavora su nuove funzionalità poco documentate.

Consigli pratici:

  • Cambia i dati di input in modo creativo e imprevedibile
  • Prova flussi non documentati o edge case
  • Annota ogni comportamento sospetto e condividi le scoperte con il team
  • Usa sessioni time-boxed per massimizzare l’efficacia

L’exploratory testing è una risorsa preziosa per scoprire problemi “nascosti” e migliorare la qualità complessiva del prodotto.

Contract test: patti chiari tra servizi 📜

I contract test verificano che le API rispettino i contratti stabiliti tra servizi. Così eviti che il microservizio A parli in klingon e il B in elfico. Sono fondamentali in architetture a microservizi e per l’integrazione con sistemi esterni.

Quando usarli?

  • Quando più team lavorano su servizi diversi che devono comunicare tra loro
  • Per garantire che le modifiche a una API non rompano i client
  • In CI/CD, per validare ogni modifica prima del deploy

I contract test riducono i rischi di regressioni “a catena” e facilitano la collaborazione tra team diversi.

Golden test: l’oro della regressione 🏅

I golden test confrontano l’output del software con risultati “d’oro” attesi. Se cambia qualcosa, suona l’allarme: a volte è voluto, a volte… no. Sono utili per testare output complessi, come report, file di configurazione o risposte API strutturate.

Quando usarli?

  • Quando l’output è difficile da validare con asserzioni semplici
  • Per garantire che modifiche future non alterino il formato o il contenuto dei risultati
  • In progetti dove la retrocompatibilità è fondamentale

Aggiorna i golden file solo quando sei sicuro che il cambiamento sia desiderato. Un golden test ben gestito è un alleato prezioso contro le regressioni silenziose.

Smoke test: accendi e spera 🚬

Gli smoke test sono i test più rapidi: controllano che il sistema si avvii e le funzioni base rispondano. Se falliscono, meglio saperlo subito che scoprirlo in produzione. Sono spesso eseguiti dopo ogni deploy o build per una verifica immediata dello stato di salute dell’applicazione.

Quando eseguirli?

  • Dopo ogni deploy o build
  • Come primo step di una pipeline di test
  • Per validare ambienti di staging e produzione

Meglio fallire subito che scoprire i problemi in produzione! Gli smoke test sono il tuo primo scudo contro i disastri.

Strategie e tecniche di testing 🛠️

Blackbox, whitebox, graybox: 50 sfumature di test 🎲

  • Blackbox: il tester non sa nulla del codice, solo cosa dovrebbe fare. Ideale per chi ama le sorprese (e i bug). Si basa sui requisiti e verifica che il sistema si comporti come atteso, senza preoccuparsi di come ci riesce.
  • Whitebox: qui si conosce tutto, anche i segreti più oscuri. Perfetto per chi non si fida nemmeno del proprio codice. Si testano tutti i percorsi logici, condizioni e cicli, per scovare bug nascosti nei meandri del software.
  • Graybox: un compromesso: sai qualcosa, ma non tutto. Come leggere la documentazione… ma solo i titoli. Utile quando vuoi testare alcune parti interne senza perdere la visione d’insieme.

Manual vs automated: chi vince? ⚖️

  • Manual: insostituibili per test di usabilità, accessibilità e casi limite. Ma attenzione: la noia e la distrazione sono sempre in agguato. Perfetti per scoprire problemi che solo un umano può notare (tipo il font Comic Sans in produzione).
  • Automated: la vera forza del testing moderno. Veloci, ripetibili, e soprattutto non si lamentano mai (tranne quando falliscono alle 3 di notte). Ideali per regression, integration e functional test ripetitivi.

Continuous testing: la ruota non si ferma mai 🔄

Il testing continuo è la chiave per evitare che il debito tecnico si accumuli come le email non lette. Ogni commit dovrebbe scatenare una valanga di test automatici: se qualcosa si rompe, meglio saperlo subito (e non il giorno del rilascio). La CI/CD non è solo moda: è sopravvivenza.

Gestione e documentazione dei test 📚

Testlist, testbook, test report: la carta non manca mai 📝

  • Testlist: la lista della spesa dei test. Utile per non dimenticare nulla (tipo il latte, o il test sul login). Serve a pianificare e tracciare cosa è stato testato e cosa manca.
  • Testbook: la Bibbia dei test: scenari, aspettative, risultati attesi. Perfetta per chi ama la burocrazia (o per quando il QA fa domande scomode). Documenta ogni test in dettaglio, così nessuno può dire “non lo sapevo”.
  • Test report: il verdetto finale. Verde? Si festeggia. Rosso? Si piange (e si indaga). Riassume i risultati dei test, evidenzia i problemi e aiuta a capire dove intervenire.

Approcci avanzati 🚀

TDD: Test Driven Development (o come scrivere test prima del codice) 🧪

Il TDD non serve solo a testare, ma a progettare meglio. Scrivi il test, poi il codice minimo per farlo passare, poi refactora. Sembra una tortura, ma ti salva da notti insonni e refactoring infiniti. E ricorda: il TDD non è una religione, ma una buona abitudine. Aiuta a mantenere il focus sugli obiettivi e a evitare di scrivere codice inutile.

BDD: Behavior Driven Development (quando i test raccontano storie) 📖

Il BDD trasforma i test in storie comprensibili anche a chi non sa programmare. Gli scenari sono scritti in linguaggio naturale, così anche il PM può capire (e criticare) cosa fa il software. Ottimo per evitare fraintendimenti e bug “a sorpresa”.

In Go puoi usare librerie come godog:

1Scenario: Login riuscito
2  Given un utente valido
3  When effettua il login
4  Then l'accesso è consentito

Mutation testing: il test che non ti aspetti 🧬

Qui si introducono mutazioni (modifiche) nel codice per vedere se i test le beccano. Se non falliscono, forse i tuoi test sono troppo gentili (o troppo distratti). È il modo migliore per scoprire se i tuoi test sono davvero “cattivi” come dovrebbero. In Go puoi usare go-mutesting, in Node.js c’è Stryker. Questi strumenti automatizzano la creazione di mutanti e ti aiutano a migliorare la qualità dei test.

Coverage: quanto sei coperto davvero? 📊

La coverage indica quanta parte del codice è coperta dai test. Ma attenzione: inseguire il 100% può portare all’over-testing, ovvero testare anche l’ovvio e l’inutile. Meglio una coverage “intelligente” che una copertura totale ma sterile. Ricorda: la qualità non si misura solo in percentuale! La coverage ti dice solo dove guardare, non se il codice è davvero a prova di bug.

1go test -cover ./...

Flaky test: il test che non si decide 🥴

Un “flaky test” è un test che a volte passa e a volte fallisce senza che il codice sia cambiato. Questo comportamento instabile mina la fiducia nella suite di test e rallenta lo sviluppo.

Cause comuni:

  • Dipendenze da servizi esterni o risorse di rete
  • Dati di test variabili o non isolati
  • Problemi di timing, race condition, concorrenza
  • Test che dipendono dall’ordine di esecuzione
  • Mancanza di cleanup tra un test e l’altro
  • Uso di orari, date o random senza controllo

Come gestirli:

  • Isola i test e usa dati deterministici
  • Mocka le dipendenze esterne
  • Esegui i test in ordine casuale per scoprire dipendenze nascoste
  • Automatizza il rilevamento dei flaky test (es. eseguendo più volte la suite)
  • Correggi o elimina i test instabili: meglio meno test ma affidabili!

Un flaky test è peggio di nessun test: se non ti fidi dei risultati, nessuno li guarderà davvero.

Checklist di errori comuni nei test ⚠️

Anche i test possono avere bug! Ecco una checklist di errori frequenti da evitare:

  • False positive: il test passa anche se il codice è sbagliato (magari perché l’asserzione è troppo debole o manca del tutto).
  • False negative: il test fallisce anche se il codice è corretto (spesso per dati di test errati o setup sbagliato).
  • Test fragili: falliscono per motivi non legati al codice (es. dipendenze esterne, dati variabili, orari, random).
  • Over-mocking: si simula troppo, perdendo il senso del test reale e rischiando di testare solo i mock.
  • Test duplicati: più test fanno la stessa cosa, aumentando la manutenzione senza valore aggiunto.
  • Test lenti: test che rallentano la pipeline e scoraggiano l’esecuzione frequente.
  • Dipendenza tra test: l’ordine di esecuzione influisce sul risultato (i test devono essere indipendenti!).
  • Dati di test hardcoded: dati statici che non coprono casi reali o edge case.
  • Assenza di cleanup: i test lasciano dati o risorse sporche, causando effetti collaterali.
  • Non aggiornare i test: dopo refactoring o nuove feature, i test non vengono aggiornati e diventano inutili o fuorvianti.

Rivedi periodicamente i tuoi test con questa checklist: la qualità dei test è importante quanto quella del codice!

Conclusione 🎯

Testare non è solo una questione di tool o percentuali: è un atto d’amore verso il futuro del tuo software (e verso chi dovrà metterci le mani dopo di te). Sii creativo, sii rigoroso, ma soprattutto… non lasciare che i bug si sentano a casa! 🐞

Ultimo aggiornamento il