Deploy Value

TDD…Perché non ci riesco?

17 Maggio 2022 - 6 minuti di lettura

Bentrovati con un nuovo resoconto dell’ultimo appuntamento organizzato dall’eXtreme Programming User Group Bergamo, la community che permettere a tutti gli appassionati, o anche solo curiosi, di eXtreme Programming e metodologie di sviluppo Agile del software di incontrarsi e scambiare esperienze e conoscenze.

Protagonista del meetup del 26 aprile è stato Luca Piccinelli, un caro amico dell’XPUG Bergamo e già ospite nell’oramai lontano 2019 con un talk dedicato al linguaggio Cobol, il quale ha trattato una tematica cara a tutti gli sviluppatori: il Test-Driven Development, più comunemente noto come TDD, una delle pratiche di eXtreme Programming più diffuse.

Luca, nel suo talk intitolato “TDD…Perché non ci riesco?” ha raccontato la sua esperienza nell’utilizzo di questa pratica, spiegandoci sia i motivi del perché la considera difficile da adottare sia i benefici che se ne traggono applicandola nello sviluppo software.

Buona lettura.

Perché è difficile adottare il TDD?

Cercando sul Web si trova tanta narrativa (e anche corsi, video tutorial ecc.), ma per Luca c’è un grosso problema di comunicazione del seguente concetto:

“Lavorare in TDD è difficile”.

Perché? Che voi seguiate un tutorial online, o leggiate blogpost, articoli, libri…spesso il messaggio che arriva è un altro, ovvero:

“Tutti lavorano in TDD, anche tu dovresti farlo”.

In fondo, che cos’è il TDD?

“Una pratica Agile, quindi ciclica, che permette di scrivere il codice che testa le specifiche del sistema prima ancora di scrivere il codice delle specifiche stesse.”

Non sembra così complicato. Una sensazione che trova conferma riprendendo il significato del “TDD Loop” che mostra in circolo le parole “Red-Green-Refactor“:

  • Red
    • Seleziona tra le feature da implementare, la più semplice.
    • Aggiungi velocemente un test.
    • Esegui tutti i test. L’ultimo fallisce.
  • Green
    • Fai una modifica, che sia piccola e quindi semplice.
    • Esegui tutti i test e osserva attentamente che tutti passino.
  • Refactor:
    • Fai refactor al codice fino a togliere tutta la duplicazione.

Possiamo identificare sei mini fasi di questo processo, che possiamo vedere come piccoli cicli ognuno dei quali fornisce preziosi feedback (a tal proposito, Luca suggerisce l’acquisto e lo studio del libro “Test Driven Development: By Example” di Kent Beck).

Ma allora…perché è difficile utilizzare TDD? Luca nella sua esperienza ha identificato quattro motivi.

  1.  Il TDD introduce un nuovo costo, dato che bisogna scrivere più codice (sia per i test sia per le funzionalità).
  2. È un approccio che non si combina molto bene con le strategie Top down e Bottom up, diffusissime e molto utilizzate per la realizzazione di applicazioni software.
  3. Che cosa testo? Bisogna stare attenti, perché lavorare in TDD partendo da una funzionalità non chiara, o da qualche aspetto che non si è capito, può portare a enormi sprechi di tempo.
  4. Esistono i database (e non solo). Nei vari talk, workshop e corsi introduttivi sul TDD che Luca ha seguito, solitamente non vengono fatti esempi di test d’integrazione…come comportarsi?

I costi nello sviluppo software

Quando parliamo di sviluppo software, quali costi individuiamo?

  • Costo di sviluppo.
  • Costo di evoluzione.
  • Costo di manutenzione.
  • Costo di sviluppo dei test.
  • Costo di manutenzione dei test.

Gli ultimi due sono costi molto importanti perché porterebbero dei benefici nei costi di manutenzione ed evoluzione, e un modo per arrivarci è adottare l’approccio TDD.

Riprendendo quanto scritto prima, se adottassimo TDD in un progetto avremmo feedback immediati e quindi benefici immediati (feedback rapido su una piccola parte dell’applicazione che sviluppo partendo dai test). Non è intuitivo ma un altro beneficio riguarderebbe proprio la prima voce, il costo di sviluppo.

Come si può fare per ottenere questi benefici?

Luca ha condotto una piccola demo prendendo un classico kata di programmazione, il “Birthday Greetings“, un esercizio in cui si chiede di scrivere il codice di un’applicazione che, ricevuto come input un file di testo contenente un elenco di impiegati (per ognuno, informazioni su cognome, nome, data di nascita ed email), invia una email di auguri il giorno del compleanno.

Dal design Top down e Bottom up al design “per funzionalità”

Ragionando secondo il modello “Top down”, Luca ha individuato le prime classi fondamentali per l’applicazione: “Employee”, “Email”, e “GreetingMessage”. L’applicazione deve inviare una email, quindi sicuramente serve una classe “EmployessFileLoader” e una classe “EmailSender”. Sicuramente bisognerà verificare la correttezza delle righe del file in input, quindi aggiungiamo al design una classe “LineParser”.
Già che ci siamo, anticipiamo nuove richieste, oggi gestiamo in ingresso un file di testo, e domani? Magari prenderemo i dati da un database. Quindi aggiungiamo alla lista una classe “IEmployeeLoader” che serve da interfaccia…e poi ancora altre classi quali “FetchMediaFactory”, “IGreetingSerializer,” “GreetingMessageTemplate” e così via.

L’avrete sicuramente notato, l’approccio “Top down” non va bene, perché tendiamo ad anticipare un possibile futuro e potenzialmente potremmo non finire mai di complicare il design.

Cambiamo metodo, e ragioniamo dividendo il problema iniziale in sottoproblemi, ovvero individuiamo le funzionalità di “Birthday Greetings”:

  • leggere la lista degli impiegati da un file di testo;
  • stabilire chi compie gli anni;
  • inviare le email alle persone il giorno del loro compleanno.

Abbiamo una visione più chiara, che peraltro ci permette di rispondere a una domanda quando lavoriamo in TDD: “Cosa testo?“.  Si testano le funzionalità! A questo punto Luca ricorda una regola importantissima:

Aggiungere un test solo se è utile al momento“.

Se sto lavorando alla funzionalità per stabilire chi compie gli anni, non mi devo preoccupare di altro, per esempio di scrivere il test che verifica il corretto invio del messaggio di auguri. Ce ne occuperemmo in un’altra iterazione del ciclo “Red-Green-Refactor”.

Un altro punto importante, che spesso fa perdere il focus sul vero valore del TDD, è di non cercare la copertura, o coverage, del codice al 100% con i test. Il TDD è un approccio che serve a fare design, la coverage verifica invece la copertura dei test. Soprattutto per chi è agli inizi con il TDD è bene concentrarsi sulla pratica e lasciare in secondo piano la coverage.

Luca, durante la sua breve demo, ha dimostrato come, applicando il TDD, il sistema cresca poco alla volta con del codice funzionante e testato, proprio perché si parte dalla scrittura dei test.
Un altro aspetto  che è emerso, decisamente più tecnico, riguarda la scrittura dei test: non si devono scrivere più file differenti per testare le diverse casistiche di errore, bensì bisogna semplificare il test separando le responsabilità nei diversi collaboratori.

Approccio iterativo incrementale e Simple Design

Con la demo Luca ha dato una dimostrazione di quello che è noto come approccio iterativo-incrementale. L’idea è di fare crescere il sistema un pezzettino alla volta, lavorando alle funzionalità utili al momento.

Un’applicazione sviluppata in TDD e con un approccio iterativo-incrementale è un’applicazione dal design semplice, e qui Luca ha introdotto un altro importante concetto, quello del Simple Design.

Per Luca la definizione di Simple design è la seguente:

Un sistema è semplice quando le interazioni degli elementi che lo compongono sono chiare e note.

Per far sì che un programma abbia un “Simple Design”, il grosso dello sforzo è concentrato su questi due punti:

  • rimuovere le duplicazioni, sia dal codice della funzionalità che dal codice del test (fase di refactoring);
  • rispettare il principio della Single Responsibility.

Conclusioni

Che cosa ha imparato Luca sul TDD?

  • È una pratica di Extreme Programming che fornisce molti feedback velocemente e garantisce la protezione del valore del codice.
  • Permette di lavorare con un approccio iterativo-incrementale, utile perché posticipa le decisioni che si riflette in costi di sviluppo ridotti (si lavora su piccoli pezzi che si reputano di valore al momento).
  • L’approccio iterativo-incrementale migliora il design, e si tende ad avere un design semplice perché poco alla volta miglioro il codice dato che ho evidenza immediata degli errori, abbassando sia il costo di evoluzione sia quello di manutenzione.

Ricordate la lista dei costi del software? Ecco come viene impattata dall’utilizzo del TDD:

  • costo sviluppo -> approccio iterativo-incrementale;
  • costo evoluzione -> Simple Design;
  • costo manutenzione -> presenza di test e Simple Design;
  • costo creazione test -> bilanciato da approccio iterativo-incrementale;
  • costo manutenzione test -> test delle funzionalità.

Prima di salutarci, Luca ci ricorda un’ultima importante verità:

Il TDD è uno strumento di sviluppo, NON uno strumento di test!

Articolo scritto da