Prefazione
Ho deciso di scrivere questo articolo in quanto finalmente ho iniziato anche io a testare il codice che scrivo adottando la metodologia del Test Driven Development (TDD), che sia per lavoro o per progetti personali e quest’articolo è la testimonianza del cambiamento che è possibile ottenere nell’adottare questa pratica.
Quindi più che essere un articolo fortemente pratico, è un annuncio a tutti coloro che ancora non testano il loro codice e che si affidano un po’ al caso, come ho fatto anche io finora d’altronde.
Chiudo questa prefazione all’articolo con una piccola avvisaglia per tutti voi che si distacca dalla programmazione e che riguarda più in generale le materie scientifiche.
Molto spesso il nostro lavoro viene visto un lavoro “per pochi”. C’è ancora in giro l’idea del programmatore pazzo e genio assoluto che scrive mappazzoni di 20k di righe di codice per file con commenti strampalati e imbarazzanti o soggetti che creano applicazioni ultra fornite che fanno anche il caffè, ma sono cosi buggate e il codice così contorto che non riescono a sostituire nemmeno la carta igienica.
Una volta per tutte sento quindi di dover dire:
In qualunque materia scientifica, il modo più sicuro per riuscire efficientemente nel produrre qualcosa di funzionante e per essere considerato un bravo/ottimo/eccellente individuo è quello di adottare una disciplina, un insieme di metodi che, attraverso determinati passi, portano alla soluzione e meno sarà affidato al caso, maggiore sarà la probabilità di riuscita. Non c’è e non dev’esserci più spazio per l’improvvisazione e gli improvvisati.
Buona lettura 🙂
Cos’è il Test driven development?
Quando parliamo di Test Driven Development, non parliamo semplicemente di testing del codice, ma di una ben specifica metodologia rinomata per le tre fasi “Red, Green, Refactor” (di cui parleremo tra poco) e per la scrittura dei test preventiva al codice.
Alcuni però offrono un’immagine alternativa del TDD definendola come la pratica per cui il codice che andrà a costituire l’applicazione viene scritto pensando prima ai test, ovvero scriviamo codice testabile.
Cosa significa però scrivere “codice testabile”? Qualunque codice potenzialmente può essere testato, ovvio; Quindi perché la necessità di specificarlo? Scrivere codice testabile significa scrivere del codice i cui test non sono più complicati del codice stesso, ma come possiamo riuscire nell’intento?
Difatti più che alla vera e propria pratica del TDD, è importante per me spostare l’attenzione e far focalizzare lo studente sul modello mentale che un developer costruisce nel pensare prima di tutto ai test. Seguendo quanto detto, il codice che risulterà sarà un codice componibile, focalizzato al soddisfacimento dei requisiti e soprattutto mantenibile, solo a questo tipo di codice possono essere associati test tanto semplici da risultare addirittura una documentazione dello stesso.
In accordo con quanto scritto nella prefazione, ciò che il TDD propone è la composizione di tante piccole porzioni di codice costruite in isolamento in un ciclo dove vengono scritti prima i test e poi il codice, il più granularmente possibile, arrivando man mano al lavoro finale. La confidenza e la sicurezza che questo lavoro finale funzioni seguendo le specifiche sono altissime grazie alla struttura di costruzione piramidale per cui ciò che scriviamo sopra è scritto sotto qualcosa il cui corretto funzionamento è accertato.
La seguente è una metodologia, d’altronde, che ha delle caratteristiche comuni con il noto branch and bound, attraverso il quale la soluzione finale è data dalla scomposizione del problema originario in sottoproblemi più semplici.
Se è cosi, perchÉ non tutti i developer testano il codice?
Quasi tutti i developer sanno che è bene scrivere test, eppure pochissimi lo fanno. Specialmente in Italia ho notato una bassissima propensione al testing automatico del codice e tanto meno alle diverse pratiche metodiche che comportano una specifica pianificazione prima di mettersi a scrivere come piccoli nerd. Ma la colpa di chi è?
- Nostra.
C’è poco da fare, siamo pigri. Nella mia vita, da parte di familiari, amici e parenti, ho sentito troppe volte la solita scusa del “non posso, ho troppe cose da fare, non ho tempo”, ma la verità per il 99% delle volte, e tranquilli che quasi sicuramente non rientrate nell’1% voi che leggete, ho constatato essere un’altra: gli impegni per cui “non abbiamo tempo” non sono semplicemente una nostra priorità.
Sta tutto nel comprendere bene cosa deve essere fatto e cosa può aspettare*, e prima includiamo la scrittura di test come un obbligo, prima beneficeremo del guadagno enorme di tempo rispetto al codice scritto senza. - Le aziende.
In moltissime occasioni il nostro lavoro è screditato: preventivi al ribasso, pagamenti che tardano e/o faticano ad arrivare, ristrettezze o totale assenza di investimenti nel nostro settore da parte dell’azienda e quant’altro… tutte cose che castrano sicuramente la voglia di crescere professionalmente, ma è una problematica generale del developer più che un qualcosa che riguarda unicamente il testing.
*Ciò è valido per qualunque ambito della nostra vita e quasi sicuramente spenderemo le nostre vite intere nel cercare di capirlo, senza mai venirne a capo del tutto.
Ma quanto costa realmente scrivere i test?
Dipende.
Nello scrivere i test capita spesso di creare rallentamenti e frizioni nel lavoro di un developer quando non si scelgono i giusti tool o quando magari si sta scrivendo del codice con l’attitudine sbagliata (come già detto sopra).
Non sarò l’ennesimo che vi ripeterà che il costo del tralasciare la scrittura di test è alto, perché non è sempre così, o almeno non lo è finché creiamo progetti personali dove la base di utenza è pressochè nulla o del tutto inesistente o lavoriamo su progetti talmente semplici che più che scrivere logica scriviamo configurazioni per qualche framework/piattaforma di sviluppo; per quest’ultimo caso, è valsa la pena creare il seguente mini paragrafo.
Cos’è che vogliamo davvero testare?
Prima di iniziare a scrivere test, vorrei mi avessero fatto capire cosa va testato e cosa no. Se da un lato può sembrarvi bizzarra la domanda, in realtà credo sia un problema che vi ritroverete prima o poi ad affrontare.
Complici anche i tanti esempi online troppo semplici che non ci lasciano ragionare nei casi reali d’utilizzo, dove i framework la fanno da padrone, si crede banalmente che qualunque micro pezzetto di codice che scriviamo vada testato senza pensare magari che il test che stiamo scrivendo è un test in realtà che appartiene al dominio di un framework/libreria in uso. Un esempio:
se abbiamo un metodo per controllare che una data sia nel passato o nel futuro e la data prima di essere utilizzata è formattata da una libreria esterna, un test per questo metodo non ha bisogno di testare che la conversione della data sia stata effettuata correttamente, ma soltanto che, fornita una data, il metodo restituisca correttamente se essa è passata o futura. Se invece fossimo stati noi a creare la funzione di formattazione, avrebbe avuto senso creare un test apposito che controllasse la corretta formattazione.
Le 3 leggi del TDD
Nel Test Driven Development ci sono 3 leggi troppo spesso rese superficiali da lettori poco attenti. Listo queste tre leggi per poi estrapolare da queste un concetto chiave della metodologia.
- Scrivi un test che fallisce prima ancora di scrivere qualunque codice applicativo (che andrà in produzione).
- Non devi scrivere più di un test (o più di un’asserzione in esso)* di quanto sia sufficiente per farlo fallire, o per far fallire la compilazione.
- Non devi scrivere più codice applicativo di quanto sia necessario per far passare il test costruito al punto precedente.
Di seguito la versione originale scritta in inglese dal grande Robert C. Martin aka Uncle Bob:
- You must write failing test before you write any production code.
- You must not write more of a test than is sufficient to fail, or fail to compile.
- You must not write more production code than is sufficient to make the currently failing test pass.
C’è un asterisco avendo inserito una piccola aggiunta nella traduzione in quanto il significato per intero, in italiano, credo sia molto più chiaro in questo modo.
La traduzione letterale di “more of a test” sarebbe “più di un test”, ma così scrivendo sembra difatti che non dobbiamo scrivere più di un singolo test (ovvero non scriverne due, tre, quattro ecc.). Il vero significato, che trova spiegazione nel concetto che stiamo per analizzare, è in effetti più fine. Il punto è di non scrivere, in ogni singolo test, non più di quanto sia sufficiente per farlo fallire; un esempio:
se vogliamo creare un metodo che faccia la somma di due numeri e poi la divisione degli stessi, non creiamo un test iniziale che asserisca sia la somma che la divisione, ma scriviamo prima la linea di test per cui la somma è verificata, e in risposta andiamo ad implementare la somma nel metodo che fa passare la linea di test appena scritta, e subito dopo scriviamo l’altra linea di test per cui la divisione è verificata, e di nuovo andiamo ad implementare la divisione nel metodo.
Vedete le due paroline in grassetto? Quelle due sono l’anima del processo ed è il principio della “line-by-line granularity” per cui minimi sono gli step, massima è la confidenza con la quale scriviamo la nostra logica.
Ammetto che può essere tedioso questo tipo di micro-scala sulla quale si applica il tutto, difatti leggendo pareri online di altre persone, sembra essere il principio in genere meno rigorosamente rispettato. Magari paga, magari no! Iniziate a testare il vostro codice in questo modo e diteci cosa ne pensate!
Il ciclo del TDD
Con l’intuizione che ciò di cui abbiamo parlato si potesse formalizzare in un ciclo, finora abbiamo illustrato solo 2 step su 3. Cos’è che rimane? Vediamo subito un immagine molto esemplificativa:
La prima fase, come già detto, è chiaramente quella della scrittura di un test minimo che fallisca (cerchio blu, in alto a sinistra). Da qui se i test passano, riscrivi il test perché probabilmente c’è qualcosa che non va. Questo test (cerchio rosso, a sinistra), verifica la cosiddetta “Test Harness“, ovvero che la suite di testing in uso stia funzionando e che, se davvero il test riesce a passare senza aver scritto codice, potrebbe essere stato scritto in maniera errata. Se invece il test fallisce, scriviamo il codice applicativo affinché questo non passi i test (cerchio blu, a sinistra). Il codice scritto in questo punto, potrà essere un codice poco elegante, sporco e pieno di imperfezioni; non ci interessa! L’importante, per ora è far passare i test. Da qui passiamo al cerchio a destra.
Di nuovo eseguiamo tutti i test scritti finora e verifichiamo se la modifica appena applicata ha causato il fallimento di altri test, ovvero se il codice appena scritto ha influenzato il resto del codice in qualche modo causando un bug. Se cosi fosse, si dice che il codice ha causato una regressione e dobbiamo modificare il codice applicativo affinché tutti i test passino di nuovo (cerchi blu, in basso a destra).
In caso invece i test passassero possiamo iniziare a fare refactoring. Come vi dicevo, l’obbiettivo di prima era di far passare i test, il nuovo obiettivo è far si che i test continuino a passare con le modifiche effettuate (cerchio blu, in alto a destra).
Una piccola nota. Se nella prima fase di scrittura del codice sbagliassimo a scrivere il test, significa che abbiamo mal interpretato le specifiche e va riscritto seguendo poi sempre lo stesso processo.
Se senza la scrittura di test dovevamo preoccuparci degli errori di interpretazione dei requisiti e degli errori logici nella costruzione del codice, ora dobbiamo “solo” fare attenzione nel comprendere bene ciò che le specifiche ci chiedono.
Non voglio mettere altra carne sul fuoco. Inizialmente pensavo di inserire anche un esempio in qualche linguaggio, ma ce ne sono davvero già moltissimi nella rete (date anche un’occhiata alla sitografia proprio qui sotto). Ho pensato, quindi, di strutturare il prossimo articolo solo con esempi pratici e mi piacerebbe creare un piccolo video che illustri il tutto.
Vi lascio e mi raccomando, date una possibilità alla vostra mente di dormire sonni tranquilli adottando il Test Driven Development 🙂
Sitografia/VideoGrafia
Di seguito alcuni link dove potrete leggere di più su quanto detto finora insieme a dei video esemplificativi dove viene mostrato un vero e proprio utilizzo in campo di questa pratica.
- The Cycles of TDD – Robert C. Martin
- Wikipedia
- The three laws of TDD (Kotlin) – Video, Robert C. Martin