Test Driven Development – Introduzione

 

Red Pill TDD

Questo articolo, sarà il primo di una lunga serie sul Test Driven Development (Sviluppo Guidato dai Test) o TDD.

Il processo di sviluppo TDD ha fatto il suo debutto circa 18 anni fa come parte integrante dell’Extreme Programming (XP) ed è ora adottato da tutti i team di sviluppo che fanno uso di metodi agili, e non solo. Io ne ho sentito parlare non più di 10 anni fa ed ero molto scettico al riguardo. All’inizio facevo delle prove per conto mio, mai in applicazioni che avrei dovuto sviluppare per lavoro. Prima il mio approccio era: analisi su carta, sviluppo, test completo, debug, correzione, test, analisi, debug, sbattimento di testa, debug, correzione, sviluppo, … e così via. Producendo software per soldi (ovvero per lavoro), non pensavo ci fosse tempo per scrivere degli Unit Test o, addirittura, partire dal test!

Adesso non rilascio una classe se prima non ci sono degli Unit Test che la coprono (torneremo in seguito sul Code Coverage) e, addirittura, non scrivo codice se prima non c’è un test che fallisce.

Ok per i test, ma non scrivere codice se non c’è un test che fallisce? Ma che significa?

Le tre leggi del TDD (secondo lo zio Bob)

  1. Non ti è permesso scrivere del codice di produzione, se non hai prima scritto un test che fallisce (RED)
  2. Non ti è permesso di scrivere più di un test che fallisce (un errore di compilazione equivale a fallimento)
  3. Non ti è permesso di scrivere più codice di produzione di quello necessario a far passare il test che hai appena scritto (GREEN)

Queste tre “leggi” costringono lo sviluppatore a rispettare uno specifico ciclo di sviluppo, solitamente molto breve. Si inizia con lo scrivere una piccola porzione di Unit Test (da ora in poi test) che testa delle funzionalità da sviluppare. Questo test prima o poi fallirà (per esempio quando sarà menzionatà una classe che non esiste ancora, un assert ad un metodo che fallisce, …), a questo punto si inizia a scrivere del codice di produzione. Ma non più di quello che serve a far passare il test.

Dopo che il test è passato è possibile effettuare del refactoring al codice, non necessariamente ad ogni test. Si è soliti definire questo ciclo RED-GREEN-REFACTORING: red e green sono i colori che solitamente si usano per indicare il fallimento o il successo di un test.

Nel mondo reale

Sono sicuro che la maggior parte di chi non ha mai provato questa metodologia e sta leggendo questo articolo, starà pensando: “che stupidaggine!”, “ma ci metterò un’eternità a sviluppare in questo modo! É uno spreco di tempo.”, “E l’analisi? Il design del codice? Ma dai!”, “Ok, si… forse a livello accademico potrà funzionare. Ma il mondo del lavoro non funziona così! Develop Develop Develop…”.

Ci scommetto la pelle che l’avete pensato!

Fermatevi un attimo a pensare: seguendo i principi del TDD, non scriverete molto codice tra un ciclo compilazione-esecuzione ed un altro. Ed è proprio questo il punto. Ogni volta che scriviamo un test e poi scriviamo codice di produzione e poi effettuiamo un refactoring: noi stiamo mantenendo il sistema sempre funzionante.

Robert C. Martin (lo zio Bob citato prima), fa un esempio interessante:

Prendete una stanza piena di persone che stanno sviluppando in TDD. Scegliete una persona a caso in qualsiasi momento. Un minuto prima, il suo codice funzionava [un ciclo è dell’ordine di pochissimi minuti, solitamente]. E non importa chi scegli o quando: il suo codice funzionava un minuto prima.

Se il suo codice funziona ogni minuto, quanto spesso vedrai usare un debugger? Risposta: non molto spesso. Basta premere un paio di volte ^Z e tornare ad uno stato funzionante, quidi provare a scrivere del codice migliore. E se non usi un debugger spesso, quanto tempo stai risparmiando? Quanto tempo spendi a debuggare? Quanto tempo spendi a fixare bug una volta che li hai debuggati? E se tu potessi ridurre questo tempo di una consistente frazione?

Benefici

Con l’esperienza che ho acquisito in questi anni, posso confermare la veradicità di queste parole. All’inizio pensavo che questa tecnica di sviluppo mi avrebbe fatto perdere tempo. Adesso non posso farne a meno perché mi sono reso conto che riesco a risolvere problemi complessi in meno tempo, riesco a produrre applicazioni robuste e non mi preoccupo più ogni volta che devo fare una modifica: mi basta far rieseguire tutti i test per capire se ho “rotto” qualcosa e dove.

Riguardo risolvere problemi in meno tempo, in effetti, scrivere unit test prima di scrivere codice di produzione equivale a dividere il problema in piccolissime parti. Il tempo che impiego a debuggare si è ridotto quasi del tutto; ormai il debugger lo uso solo per risolvere problemi nei test di integrazione. Se il committente mi richiede delle modifiche o se voglio effettuare un refactoring del codice, vado sul sicuro: rilancio i test e mi accorgo subito se c’è un problema. Se un test fallisce e se il test è stato scritto bene, è chiaro dove intervenire e come.

Certezza e coraggio

Adottando il TDD nella sviluppo di applicazioni, ci si troverà a scrivere centinaia di test ogni settimana. Tutti questi test saranno a portata di mano e sarà possibile eseguirli ogni volta che effettuiamo una modifica al codice.

Nel mio ultimo lavoro ho sviluppato una libreria in TDD, scrivendo circa 200 test. Ultimamente si è reso necessario aggiungere alcune nuove funzionalità. Con Maven, ogni volta che ho dovuto preparare il JAR della mia libreria da passare ai miei colleghi venivano eseguiti tutti e 200 gli unit test che coprono oltre il 90% del codice (analizzato con il test coverage del mio IDE). Gli unit test vengono eseguiti in circa 9 secondi.

Se i test passavano, avevo un certo grado di certezza che le modifiche non avevano introdotto bug.

Non vi è mai capitato di dire: “Questo codice è un pasticcio, devo sistemarlo”, ma poco dopo pensare: “Meglio non toccare questo codice!”. Perché? Perché sapevate che era un tal pasticcio di codice che avreste rischiato di rompere qualcosa.

Ma se invece ci sarebbe stata la certezza che non avreste rotto niente? Immaginate: correggete il codice, fate un bel refactoring, e poi vi basta cliccare un pulsante, attendere una manciata di secondi per sapere se avete rotto qualcosa.

Questo è uno dei benefici del TDD. Quando si ha una suite di test, spariscono tutti i timori nell’effettuare una pulizia del codice. Un codice publito ed elegante è facile da capire, facile da cambiare e facile da estendere! I difetti si riducono perché il codice diventa semplice.

Defect Injection Rate

Adottando il TDD, l’introduzione di difetti e bug nel codice si riduce. Per esperienza posso confermare questo fatto: mi sono reso conto che alle applicazioni che sviluppo in TDD (ormai tutte), mi vengono segnalati meno difetti se confrontate con le applicazioni che rilasciavo precendemente (un rate più basso se cosidero difetto/linee_di_codice, ad esempio).

Ma non è solo una mia sensazione, ci sono vari studi al riguardo e uno di questi è il seguente:

 Documentazione

Quale parte di un manuale di programmazione ci attira maggiormente? Solitamente i codici di esempio. Se vogliamo sapere come usare del codice, abbiamo bisogno di codice reale.

Ogni unit test che viene scritto seguendo le tre leggi del TDD è un esempio di codice scritto. Descrive come il sistema andrebbe usato. Seguendo le tre leggi, c’è uno unit test che descrive come creare ogni oggetto nel sistema, ogni modalità in cui un oggetto può essere creato. C’è uno unit test che descrive ogni metodo e come andrebbero chiamati. Per ogni cosa che è necessario conoscere, esiste uno unit test che descrive in pratica come fare.

Gli unit test sono documentazione. Sono la migliore documentazione che si può fornire ad uno sviluppatore.

Design

Quando vengono seguite le tre regole del TDD e vengono scritti i test per primo, si affronta un dilemma. Spesso si conosce esattamente che codice scrivere, ma le tre regole ci impongono di scrivere prima uno unit test che fallisce perché tale codice non esiste. Questo significa che bisogna testare del codice che si andrà in seguito a scrivere.

Il problema del testare il codice è che va isolato. É difficoltoso testare una funzione che ne chiama altre. Per scrivere quel test è necessario escogitare un modo per disaccoppiare la funzione dalle altre. In altre parole, la necessità di scrivere prima il test ci costringe a pensare ad un buon design!

Se non c’è bisogno di scrivere il test per primo, non c’è niente che non ci permetta di scrivere le nostre funzioni in maniera interdipendente (coupled) con altre in una massa instabile.

Le tre regole ci costringono a scegliere la migliore progettazione per la nostra applicazione.

Lezioni di Test Driven Development

Spero di avervi incuriosito. Se davvero i benefici del TDD sono quelli elencati sopra, sarebbe davvero poco professionale non adottare questa metodologia. Se avrete la pazienza di aspettare, nei prossimi giorni pubblicherò una serie di articoli sul Test Driven Development. Partirò dalle basi, fino ad arrivare ad argomenti avanzati.

Stay tuned! 😉

 

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *