TDD Test Driven Development

Test-Driven Develpoment

Il Test Driven development, abbreviato in TDD, è una tecnica di sviluppo software effettuata mediante la scrittura di test che precede lo sviluppo della funzionalità applicativa.

Il test driven development è una metodologia di design guidata dallo sviluppo di Unit tests.

E’ bene sottolineare che è una tecnica di sviluppo software (non è una tecnica di testing).

Questi tests di unità possono essere “verificati”: barra verde

oppure “non verificati”: barra rossa.

Come è indicato dal nome: test driven development è il test che guida lo sviluppo, quindi i tests sono scritti prima del codice che implementerà la funzionalità richiesta.

Lo sviluppo per mezzo del TDD si articola nei seguenti passi:

  • Scrivere il test

  • Scrivere l’implementazione della funzionalità

  • Effettuare il refactoring

Ecco questo è il ciclo di sviluppo del software con questa metodologia di sviluppo.

Se dopo l’implementazione della funzionalità e dopo il refactoring il test è passato: barra verde si inizia nuovamente a scrivere un nuovo test.

Tale procedimento è ciclico, questo comporta che tutti i precedenti tests devono essere verificati.

Il refactoring consente il miglioramento del design ed è parte integrante ed importante del TDD.

TDD-Pavia
Il microciclo del Test Driven Development

La grande differenza tra il TDD e l’ingegneria del software tradizionale è che nel TDD il test viene scritto prima di scrivere la funzionalità, mentre nell’ Ingegneria del Software tradizionale il test è successivo alla implementazione della funzionalità.

Il TDD ha i seguenti vantaggi:

  • consente di individuare in breve tempo e con immediatezza le parti di codice che non hanno il comportamento corretto.

  • Consente sviluppo di codice funzionante

  • Consente di avere codice pulito (Clean code), affidabile e funzionante.

  • Consente il testing dei comportamenti di singoli componenti software

  • Consente di avere sicurezza e confidenza nel codice da parte dei sviluppatori, perchè è una tecnica di sviluppo incrementale. Questo consente una ulteriore confidenza durante la fase di refactoring.

  • Per chi ha conoscenze avanzate di design consente un miglioramento consistente del codice esistente, cioé poichè i tests di unità verranno verificati.

  • Consente il rilascio di features funzionanti.

  • Permette allo sviluppatore di pensare in termini di requisiti e di design prima e successivamente in termini di codice funzionante.

  • Si effettuano gli “small steps”, questo approccio effettuato con semplicità consente lo sviluppo di codice funzionante.

  • Il refactoring è più efficace e più efficente ed consente un miglioramento considerevole del design e quindi anche una ottimizzazione delle performance. Il vambiamento ha un costo inferiore.

  • Si dedica molto meno tempo per il debug.

Quando si scrive il test prima del codice applicativo si effettua il TFD Test First Development (anche chiamato TFP Test First programming), ovvero scrivi il “test code” prima del “functional code”.

Successivamente si effettua il Refactoring.

Quindi il TFD è una parte del TDD.

Quando si sviluppa in TDD, soprattutto all’inizio si deve fare attenzione a…

Sviluppare in TDD! Cosa vuol dire questo? Spesso chi inizia a sviluppare in TDD soprattutto all’inizio può capitare volontariamente o involontariamente di scrivere il codice applicativo e poi il test, come avrete capito in questo caso non si sviluppa più in TDD. Lo sviluppo in TDD necessita di una curva di apprendimento alta e di una buona disciplina. Solitamente i manager sono restii ad adottarlo poichè pensano che sia una tecnica di testing (falso!, Il TDD è una tecnica di sviluppo).

Esistono due semplici regole d’oro per il TDD:

  • Scrivere nuovo business code quando i tests automatici falliscono.
  • Eliminare qualsiasi duplicazione che trovi.

Questo semplice sistema ha un impatto su un sistema complesso: gli individui e i gruppi.

Lo sviluppo del codice è incrementale e hai continui feedbacks dal codice stesso e questo impatta anche le decisioni dei requisiti (prese dalle interazioni tra le persone).

Questa metodologia di sviluppo software consente uno “sviluppo continuo”, non devi aspettare il team di test che ti scriva i tests e poi tu devi fare il fix dei bugs, il “bug fixing”.

Il cambiamento delle specifiche ti consente anche un rapido cambiamento del software.

Riesci a mantenere una alta coesione e un basso accoppiamento, riesci a tener sotto controllo questa regola di design, quindi il tuo design è altamente normalizzabile.

Sia l’evoluzione, sia la manutenzione dell’applicazione è notevolmente facilitata.

Si ha una maggior confidenza diffusa nel software.

Stimola la voglia di comunicare

Si lavora con meno stress.

 

Il test di unità è composto da:

  • Fixture Setup
  • SUT System Under Test
  • Verify Result

 

 

UnitTest2

 

 

I test di unità che gli sviluppatori scrivono devono soddisfare la seguente checklist:

  • devono essere veloci, la velocità è importante poichè i tests devono essere un mezzo, un aiuto allo sviluppatore

  • devono essere eseguibili singolarmente

  • devono essere leggibili e si devo capire facilmente

  • devono usare dati reali (è importante che siano dati reali, come quelli in produzione)

  • ogni singolo test deve essere un step “propedeutico” al requisito finale da soddisfare.

Ricorda che “il fallimento è un progresso”.

Si, hai letto bene il fallimento è un progresso, ricorda che quando vedi che il test fallisce (la barra rossa) tu “sei in progresso”, ovvero hai individuato il punto su cui focalizzarsi per risolvere il problema. Il TDD aumenta la tua confidenza e sicurezza nel sistema, in un sistema funzionante, perchè scrivi un nuovo test solo quando tutti i tests precedenti sono funzionanti (barra verde).

“Testa con uno scopo”, si “test with a purpose”, i tests devono avere uno scopo, in ogni momento che scrivi un test fatti queste due domande: so che cosa sto testando? So qual’è il livello corretto di test che sto scrivendo?

La risposta a queste due domande ti consentiranno di essere focalizzato sulla efficienza ed efficacia dei tests.

Potresti arrivare al punto in cui testi ogni singola linea di codice di una funzione, di un metodo, ecc. in questo caso la parte di codice testato è superiore sicuramente al codice testabile con tecniche di testing (e non di sviluppo).

Un buon approccio è incominciare a scrivere un test semplice e mantenere i tests più semplici possibili. Se trovi un bug o ti viene segnalato un bug aggiungi test per quel bug. Se ti blocchi può essere che stai complicando e quindi regredisci a barra verde oppure cancella e riscrivi il test dall’inizio. Se i test denono settare oggitti complessi allora valuta la possibilità di utilizzare i Mock Objects. Ricorda di non effetuare il commit di test non funzionanti.

Se il test per funzionare ha anche una sola delle seguenti caratteristiche:

  • comunica con il database
  • comunica con la rete
  • comunica con il filesystem
  • vi è una configurazione manuale

allora non è uno Unit Test, non è un test unitario, ma è un test di integrazione. Se li codice necessita di molti test di integrazione spesso non si ha un buon design e quindi non si ha un codice ben coeso e ben accoppiato.

E’ tipico degli sviluppatori non leggere la documentazione o non leggerla con accuratezza, questa perchè spesso non è aggiornata e quando lo è spesso non comprende tutti gli aspetti evolutivi o anche solo di configurazione nel momento stesso in cui è stata sviluppata. Questo comporta il fatto che gli sviluppatori guardano la documentazione vera… il codice!!!

Si, la documentazione è… la documentazione!

La documentazione vera è il codice!

Quale migliore documentazione di un codice ben scritto,

ma soprattutto quale migliore documentazione di un test ben scritto,

ma soprattutto quale migliore documentazione di un test in TDD ben scritto.

Si, gli unit tests sono la documentazione ed esplicitano che cosa fa il codice applicativo.

Si ha un parallelismo: i tests di accettazione esplicitano quello che gli stakeholders vogliono dal sistema.

Ecco allora che i tests di unità e i tests di accettazione dovrebbero consentire la scrittura della documentazione per i/altri sviluppatori stessi e per gli stakeholders.

Il TDD come abbiamo visto è una tecnica di sviluppo incrementale, ovvero permette di scrivere software a piccoli passi.

Questa tecnica di sviluppo è più produttiva di altre tecniche.

Nel caso di aggiunta di nuovo codice si capisce immediatamente se le funzionalità esistenti sono state “rotte” oppure no. E nel caso in cui i test falliscono si può immediatamente identificare il problema. E’ un buon approccio quello di modificare e o aggiungere un numero minimo di righe di codice (meno di 20/15 linee di codice) e poi rieseguire i tests, se falliscono si regredisce allo stato del codice iniziale e si vericano i tests (devono dare tutti nuovamente barra verde).

Si è effettuato un buon sviluppo in TDD quando:

  • il sistema è funzionante (importante)
  • non vi duplicazione di codice (importante)
  • alle componenti del sistema è stato applicato il principio di minima visibilità
  • ogni funzione/metodo ha un numero di linee di codice inferiore a 10
  • hai un’ alta coesione e un basso accoppiamento

Si è effettuato un cattivo sviluppo in TDD quando:

  • si testano componenti private (ad es. metodi)
  • si testano più le classi ereditate che le interfacce
  • si deve configurare l’ambiente di sviluppo prima dell’esecuzione dei tests (i tests di unità non dovrebbero contenere alcun tipo di configurazione, altrimenti non sono test unitari)
  • i tests sono lenti

Riassumendo ecco il ciclo del TDD:

  • Aggiungi un test
  • Esegui tutti i tests e vedi se fallisco dei tests
  • Scrivi codice per permettere ai tests di aver successo
  • Esegui test automatizzati e vedi cosa succede
  • Refactoring del codice

 

Alessandro Ceseno

PS: se hai domande scrivimi pure per mezzo della sezione contatti.