test driven development in software testing

test driven development in software testing

Lunedì mattina, ore 9:00. Il lead developer entra in ufficio convinto di aver svoltato perché il team ha finalmente adottato il Test Driven Development In Software Testing come dogma aziendale. Dodici mesi dopo, il progetto è in ritardo di un trimestre, il refactoring di una semplice funzione di login richiede tre giorni e la suite di test è diventata una zavorra di cinquemila righe di codice fragile che si rompe ogni volta che qualcuno cambia il nome di una variabile privata. Ho visto questa scena ripetersi in startup milanesi e in grandi centri di consulenza a Roma: team che scrivono test per dimostrare che il codice funziona, invece di usarli per progettare il software. Il costo di questo errore non si misura solo in ore uomo sprecate, ma nella perdita totale di agilità. Se il tuo processo di testing ti impedisce di rilasciare una patch in produzione entro un'ora, non stai facendo ingegneria del software, stai costruendo una prigione di codice.

Scrivere test per l'implementazione invece che per il comportamento

Uno degli errori più sanguinosi che ho osservato è la tendenza a testare "come" il codice fa qualcosa invece di "cosa" fa. Quando inizi a scrivere un test che verifica se una specifica funzione interna viene chiamata esattamente tre volte con quei parametri precisi, ti sei appena scavato la fossa. Hai legato il test all'implementazione attuale. Per un ulteriore punto di vista, consulta: questo articolo correlato.

Il risultato è che domani, quando troverai un modo più efficiente per scrivere quella logica, il test fallirà anche se il risultato finale è identico. Questo trasforma il refactoring in un incubo burocratico. Invece di migliorare il sistema, passi il pomeriggio a rincorrere test che non segnalano bug reali, ma solo cambiamenti strutturali. La soluzione è spostare il focus sul comportamento esterno. Un test utile dice: "Se inserisco queste credenziali, ricevo un token di accesso". Non gli interessa se il token viene generato da una libreria esterna o da un algoritmo scritto a mano in una funzione privata. Devi testare i confini del tuo modulo, non le sue viscere.

Il costo del micro-testing ossessivo

Spesso si crede che una copertura del cento per cento sia il segno di un progetto sano. È un'illusione pericolosa. Ho visto aziende spendere il 40% del budget di sviluppo per coprire getter e setter o logiche banali che non si romperanno mai. La verità è che i test hanno un costo di manutenzione. Ogni riga di test che scrivi è una riga che dovrai leggere, capire e aggiornare in futuro. Se il test non protegge una logica di business complessa o un punto critico di integrazione, è solo rumore statistico che gonfia le metriche ma svuota il conto in banca. Ulteriori approfondimenti su questo tema sono state pubblicate su HWUpgrade.

L'illusione della copertura totale e il fallimento del Test Driven Development In Software Testing

Molti manager si sentono sicuri quando vedono un report di code coverage che segna il 95%. Non capiscono che la copertura misura solo quali righe di codice sono state eseguite durante i test, non se quelle righe sono state verificate correttamente. Ho visto intere suite di test passare con successo mentre il software falliva miseramente in produzione perché nessuno aveva testato i casi limite o le interazioni asincrone. Usare il Test Driven Development In Software Testing come una semplice lista di controllo per la copertura è il modo più veloce per generare un falso senso di sicurezza.

La soluzione reale è l'analisi dei mutanti o, più semplicemente, un approccio basato sul rischio. Devi chiederti: "Se questa riga di codice fosse sbagliata, quale test fallirebbe?". Se la risposta è "nessuno", la tua copertura è carta straccia. Non serve testare tutto, serve testare ciò che conta. Un test su un algoritmo di calcolo delle tasse vale dieci volte un test su un controller che sposta solo dati da un punto A a un punto B.

Priorità basata sull'impatto economico

Nella mia esperienza, il successo arriva quando smetti di contare i test e inizi a valutare l'impatto di un fallimento. Se un bug in un modulo costa diecimila euro l'ora in termini di mancati ricavi, quel modulo merita test granulari e complessi. Se un bug in un'altra area causa solo un disallineamento estetico di tre pixel che viene corretto con un refresh, forse scrivere dieci test unitari lì è un pessimo investimento finanziario.

Ignorare il design a favore della meccanica del rosso-verde-refactor

Il ciclo classico del TDD sembra semplice: scrivi un test che fallisce (rosso), scrivi il codice minimo per farlo passare (verde), pulisci il codice (refactor). Il problema è che molti saltano l'ultima parte o la fanno superficialmente. Il risultato è quello che chiamo "codice a stratificazione di sedimenti": una serie di pezze una sopra l'altra che funzionano ma che nessuno capisce più.

👉 Vedi anche: cute hole c o m

Il processo non serve a creare test, serve a forzarti a pensare al design prima di scrivere la logica. Se scrivere il test è difficile, significa che il tuo codice è accoppiato male. Se hai bisogno di fare il mock di dieci oggetti diversi per testare una singola classe, il problema non è il test, è la classe che sta facendo troppe cose. Invece di cercare librerie di mocking più potenti, dovresti fermarti e spezzare quella classe in componenti più piccoli e indipendenti.

Un confronto pratico tra approccio errato e corretto

Immaginiamo di dover implementare un sistema di sconti per un e-commerce.

L'approccio errato, quello che vedo fare spesso dai team che hanno fretta, consiste nello scrivere un test che verifica internamente se la classe DiscountManager chiama il metodo db.GetCustomerType(). Il test si aspetta che venga restituito "Gold" e poi verifica che il calcolo finale sia il prezzo meno il dieci per cento. Questo test è fragile. Se decidi di spostare le informazioni del cliente in una cache o di cambiare il modo in cui il tipo di cliente viene recuperato, il test si rompe. Hai creato una dipendenza diretta tra la struttura del tuo database e la tua suite di test.

L'approccio corretto ignora il database. Il test definisce un input (un cliente con determinate caratteristiche) e si aspetta un output (il prezzo scontato). Non importa come DiscountManager ottiene le informazioni. Potrebbe leggerle da un file, da una API o calcolarle al volo. Scrivendo il test in questo modo, hai la libertà di cambiare l'intera infrastruttura sottostante senza toccare una singola riga di test. Questo è il vero potere del processo: ti dà la fiducia necessaria per cambiare idea senza la paura di rompere tutto. Nel primo caso, ogni cambiamento strutturale richiede il doppio del lavoro perché devi aggiornare sia il codice che i test. Nel secondo caso, il test diventa il tuo paracadute mentre fai refactoring profondo.

L'abuso dei Mock e la creazione di test che mentono

I Mock sono strumenti potenti, ma nel contesto del Test Driven Development In Software Testing sono spesso usati come morfina per coprire un dolore che andrebbe curato alla radice. Quando simuli ogni singola dipendenza, finisci per testare il tuo codice in un vuoto pneumatico che non somiglia affatto all'ambiente di produzione. Ho visto sistemi dove tutti i test unitari passavano in tre secondi, ma il sistema crashava all'avvio perché le configurazioni dei database reali erano diverse da quelle simulate nei mock.

L'errore è simulare ciò che non possiedi o ciò che è troppo complesso. Invece di creare mock complicati per una API di terze parti, scrivi un adapter. Testa la tua logica contro l'adapter (che è semplice da simulare) e poi scrivi pochi, mirati test di integrazione che verificano che l'adapter parli davvero con l'API esterna. Questo approccio riduce drasticamente il numero di bug che sfuggono ai test unitari e arrivano in produzione.

📖 Correlato: cover tablet ipad air

Quando il mock diventa un ostacolo

Se passi più tempo a configurare gli oggetti simulati che a scrivere la logica del test, fermati. È un segnale inequivocabile che la tua architettura è troppo complessa. I programmatori esperti preferiscono spesso usare dei "fakes" (implementazioni reali ma leggere, come un database in memoria) piuttosto che mock dinamici che verificano le interazioni. I fake tendono a essere più stabili e rendono i test molto più leggibili per chiunque arrivi nel progetto dopo di te.

Test lenti che uccidono la produttività degli sviluppatori

Un test che impiega trenta secondi per girare è un test che non verrà eseguito spesso. Se la suite completa richiede venti minuti, gli sviluppatori inizieranno a saltarla, aspettando che sia il server di Continuous Integration a trovare gli errori. Questo distrugge completamente il ciclo di feedback rapido che è il cuore dell'approccio.

In un progetto reale su cui ho lavorato, abbiamo ridotto il tempo di esecuzione della suite di test da quindici minuti a quaranta secondi semplicemente eliminando accessi inutili al disco e parallelizzando i test che non avevano dipendenze condivise. Quei quattordici minuti guadagnati per ogni singola esecuzione hanno cambiato radicalmente la velocità di rilascio del team. Se i tuoi test sono lenti, non è un fastidio minore; è un blocco operativo che sta prosciugando le risorse dell'azienda.

  1. Isola i test che richiedono IO (database, rete, file system) in una suite separata.
  2. Assicurati che i test unitari siano puri calcoli in memoria che girano in millisecondi.
  3. Esegui la suite veloce dopo ogni piccola modifica e quella lenta solo prima di un commit o di una pull request.

Controllo della realtà

Smetti di pensare che questo metodo sia una bacchetta magica. Non lo è. Richiede una disciplina ferrea e, onestamente, una capacità di astrazione che non tutti gli sviluppatori possiedono fin da subito. Se pensi di poterlo adottare semplicemente imponendo una regola dall'alto senza investire nella formazione del team sul design del software, fallirai. Costa caro in termini di tempo iniziale — circa il 15-20% in più nello sviluppo delle singole funzionalità — e se non lo fai bene, quel costo non verrà mai recuperato attraverso una minore manutenzione.

Il successo non arriva perché "fai i test", ma perché i test ti costringono a scrivere codice migliore, più modulare e meno accoppiato. Se dopo sei mesi il tuo codice è ancora un groviglio di dipendenze difficili da sciogliere, significa che hai usato i test solo come documentazione di un disastro, non come strumento di progettazione. La dura verità è che la maggior parte dei team non ha bisogno di più test, ha bisogno di test migliori e di una comprensione più profonda di cosa significhi manutenere un sistema sul lungo periodo. Se non sei disposto a buttare via i test che non servono più e a riscrivere quelli che ti rallentano, rimarrai bloccato in un ciclo di mediocrità tecnica che nessuna metodologia potrà salvare.

LV

Luca Vitale

Da anni Luca Vitale racconta politica, economia e società con uno stile diretto e una forte attenzione alle fonti.