Scroll Top
Via Antonio Amato, 20/22 84131 Salerno (SA)

Test Driven Development (TDD): una metodologia agile

Test Driven Development

(articolo redatto da Andrea Pilato)

Il Test Driven Development (TDD) è una metodologia di test, dove l’idea è quella di scrivere i test prima di scrivere il codice di produzione. Fa parte dei metodi agili, che si prefissano l’obiettivo di risolvere i problemi delle metodologie precedenti, più rigide e costose.

CHE COS’È IL TESTING

Il collaudo di un prodotto software è un’attività imprescindibile, senza di essa infatti un qualsiasi prodotto risulterebbe nella maggior parte dei casi non utilizzabile.

Il testing, inteso tradizionalmente come modello waterfall, viene eseguito al termine di un progetto, quando spesso, per rientrare nelle scadenze previste l’attività di testing, viene eseguita superficialmente, con un alto rischio di mandare in produzione un prodotto che non soddisfi le richieste.

È proprio attraverso il testing che vengono a galla i difetti del prodotto, rivelandone i malfunzionamenti. Data l’importanza di questa fase, è comprensibile come essa occupi circa il 40% del costo totale di produzione di un prodotto.

Costo di produzione del software
Figura 1: Costo di produzione del software

EVOLUZIONE DEI PRINCIPI DEL TESTING

Ora che abbiamo una conoscenza base del testing e della sua importanza, vediamo come si è evoluto nel tempo:

  • 1970: Si iniziano a definire i fondamenti teorici e approcci sistematici al testing. L’obiettivo è quello di dimostrare la correttezza dei programmi, con test perfetti quando non ci sono errori.
  • 1980: Nascono i primi standard, con conseguente cambio di mentalità. L’obiettivo è quello di trovare errori, con la conseguenza che un test che non ne rivela è un test fallito.
  • 1990: Cresce il connubio tra testing e qualità. Si affrontano nuove sfide come l’abbattimento dei costi della fase di test, come automatizzarne i processi ecc.

IL TESTING PRIMA DEL TDD

Come anticipato, prima della modalità agile, lo sviluppo del software avveniva in waterfall. Questo approccio prevede che le varie fasi per la produzioni di un prodotto siano a se stanti, passando alla successiva al termine della corrente.

Fasi del modello waterfall
Figura 2: Fasi del modello waterfall

La fase di sviluppo è completamente separata dal testing, questo permette agli sviluppatori di produrre più velocemente e di non avere meno carico lavorativo, dato che saranno demandati al team di tester.

A primo impatto potrebbe sembrare un approccio vincente, con gli sviluppatori che producono a pieno ritmo il codice che andrà in produzione, basato su delle specifiche date dalle fasi precedenti. Se teoricamente ogni fase dovrebbe essere priva di errori, nella realtà questo non succede, arrivati al testing gli errori vengono trovati tutti insieme, interrompendo il tutto il processo di sviluppo per ripartire dalla definizione dei requisiti.

COME IL TDD RISOLVE I VECCHI PROBLEMI

Il TDD è una delle 12 pratiche dell’extreme programming, che si basa su due principi:

  1. Test first programming, ovvero la stesura dei test avviene prima di quella del software;
  2. Refactor continui.

Intendere il TDD come esecuzione dei test prima dello sviluppo della funzionalità però è un modo semplicistico di spiegarlo, poiché definisce un vero e proprio cambio di mentalità, in cui occorre partire dal risultato attesto dall’utente.

Il TDD si basa su tre semplici regole:

  • Non è permesso sviluppare codice di produzione finché non è stato scritto un test che fallisca;
  • È sufficiente scrivere test che porti ad un fallimento o un errore di compilazione;
  • Non è permesso scrivere più codice di quello sufficiente a far passare il test che fallisce.

Per poter meglio rispettare i principi del TDD possiamo seguire tre punti cardine:

  • Test Early;
  • Test Ofter;
  • Test Automatically.

Test Early

Nel momento in cui deve essere aggiunta una nuova funzionalità al programma, lo sviluppatore prima di tutto definisce un primo caso di test, dopodiché passa alla definizione dell’interfaccia (sotto test), e solo dopo va ad implementare la funzione, limitando la scrittura del codice fino alla corretta esecuzione del test. Verificato che il nuovo test dia esito positivo, vengono rieseguiti i test definiti, che in caso di esito negativo prevedono la ricerca e la risoluzione dei bug nell’implementazione.

Quando tutti i test definiti risultato correttamente superati, si può procedere all’attività di refactoring, che ha come obiettivo quello di avere un codice performante e snello. Terminato il refactoring si eseguono nuovamente i test e in caso positivo può ritenersi completato il lavoro sulla nuova funzionalità.

 Uno dei vantaggi è la sicurezza che lo sviluppatore abbia ben compreso i requisiti della funzione prima dell’implementazione,  dovendo scrivere prima i casi di test. Non collocando più l’attività di testing alla fine, la scrittura dei test avviene nel modo più completo possibile, viene rilevata una buona quantità di bug e la qualità del software che viene consegnato è molto elevata. Infine, dovendo testare a priori un singolo metodo per volta, lo sviluppatore è portato a progettare il codice in parti poco interdipendenti tra loro, garantendo al software una certa modularità, oltre che una facilità nel riuso e nella manutenzione.

Dai processi precedenti, traspare il ciclo di vita del TDD:

Ciclo di vita TDD
Figura 3: Ciclo di vita

La fase di sviluppo è completamente separata dal testing, questo permette agli sviluppatori di produrre più velocemente e di non avere meno carico lavorativo, dato che saranno demandati al team di tester.

A primo impatto potrebbe sembrare un approccio vincente, con gli sviluppatori che producono a pieno ritmo il codice che andrà in produzione, basato su delle specifiche date dalle fasi precedenti. Se teoricamente ogni fase dovrebbe essere priva di errori, nella realtà questo non succede, arrivati al testing gli errori vengono trovati tutti insieme, interrompendo il tutto il processo di sviluppo per ripartire dalla definizione dei requisiti.

Test Often

Come si evince, i test non vengono eseguiti una volta sola. Ad ogni aggiunta o modifica di una funzione vengono eseguiti non solo i test sulla parte modificata, ma anche quelli sul resto del software. La modifica alla funzione, o l’aggiunta di un metodo, verrà integrata nella versione corrente del software solo quando tutti i test risultano superati con successo.

Uno dei benefici di questa direttiva è la facilità di individuazione di bug poiché, eseguendo il test ad ogni piccola modifica del codice, è più facile circoscriverne la causa. Ripetere più volte il test consente di evitare la manifestazione nel software di bug precedentemente risolti che si ripresentano (test di regressione).

Test Automatically

Porre l’attività di testing al primo posto nel ciclo di sviluppo del software permette di ottenere una versione finale del prodotto richiesto di buona qualità, d’altro canto dover eseguire i test molto frequentemente potrebbe portare ad un rallentamento dei tempi di sviluppo. Questa problematica è solo apparente, infatti i test devono essere implementati in modo da poter essere eseguiti in automatico (cioè senza l’intervento dell’utente), con la possibilità di lanciare tutti i casi di test definiti con un singolo comando.

UN CASO D’USO REALE DEL TDD

Passiamo ad un esempio concreto, per vedere come mettere in pratica il TDD. Supponiamo il caso di dover calcolare i fattori primi di un numero, ovvero tutti i numeri primi che lo dividono esattamente con resto zero.

Es. fattore primo di 1 = 0; fattore primo di 2 = 2; fattori primi di 6 = 2, 3; fattori primi di 12 = 2,2,3.

Passiamo ora all’implementazione della nostra classe di test, che chiameremo PrimeFactorsTest, il cui primo test sarà un test vuoto.

Test vuoto
Test vuoto

Il primo test reale da aggiungere è quello per il numero 1, quindi definiamo un metodo “factorsOf” che prende in ingresso un numero intero e restituisce una lista di interi (i fattori primi del numero in input). Come da definizione il fattore primo di 1 è l’insieme vuoto, per noi una lista vuota.

Test con 1
Test con 1

Testiamo per 2, aspettandoci come risultato 2, ma con il codice attuale avremo una lista vuota, modifichiamo quindi il codice aggiungendo la nuova casistica.

Test con 2
Test con 2

Aggiungendo il caso di test per il numero 3, abbiamo 3 come risultato atteso, ma invece il nostro codice restituisce 2, quindi andiamo a modificare il codice.

Test con 3
Test con 3

Al test per 4 ci aspettiamo 2,2 come risultato ma abbiamo 4, apportiamo quindi le modifiche necessarie.

Test con 4
Test con 4

A questo punto proseguiamo con i test per  5, 6 e 7 i quali sono tutti correttamente funzionanti con il codice attuale.

Test con 4, 5, 6 e 7
Test con 4, 5, 6 e 7

Dal test per  8 ci aspettiamo 2,2,2 ma il valore restituito è 2,4 apportiamo ulteriori modifiche.

Test passato con 8
Test passato con 8

Con n=9 il risultato atteso è 3, 3 ma avremo 9. La prima modifica per far passare il test è quella di replicare il codice.

Test con 9 passato
Test con 9 passato

Il test è passato, ma il codice è ripetitivo e non risolve un problema generico ma solo dei casi specifici, effettuiamo quindi delle operazioni di refactor.

Codice rifattorizato 1.0
Codice rifattorizato 1.0

A questo punto il codice è sì generico, ma notando meglio le condizioni è possibile notare come ci siano due if(n>1), poiché il secondo può essere portato fuori dal while al livello dell’altro if, può essere eliminato, e i test continueranno a passare.

Codice rifattorizzato 1.1
Codice rifattorizzato 1.1

A questo punto vediamo come il codice è pulito e risolve problemi generici. Proviamo con un numero molto grande (2x2x3x3x5x7x11x11x13) e vediamo che il risultato è quanto atteso.

Test con 2x2x3x3x5x7x11x11x13
Test con 2x2x3x3x5x7x11x11x13

Conclusioni

Testa anche tu il codice con i seguenti requisiti:

  • dato un input n compreso tra 1 e 100, il codice dovrà stampare tutti i numeri naturali compresi tra 1 ed n;
  • al posto dei numeri divisibili per 3 dovrà stampare la stringa “Nex”;
  • al posto dei numeri divisibili per 5 dovrà stampare la stringa “Soft”;
  • al posto dei numeri divisibili sia per 3 che per 5 dovrà stampare la stringa “NexSoft”;

Per n = 16 il risultato atteso è il seguente: 1, 2, Nex, 4, Soft, 6, 7, ….., 14, NexSoft, 16.


Se anche tu vuoi occuparti di progetti di Software Testing
dai un’occhiata alle nostre opportunità di lavoro e conosciamoci subito!

Questo sito utilizza cookies propri e si riserva di utilizzare anche cookie di terze parti per garantire la funzionalità del sito e per tenere conto delle scelte di navigazione.
Per maggiori dettagli e sapere come negare il consenso a tutti o ad alcuni cookie è possibile consultare la Cookie Policy.

USO DEI COOKIE

Se abiliti i cookie nella tabella sottostante, ci autorizzi a memorizzare i tuoi comportamenti di utilizzo sul nostro sito web. Questo ci consente di migliorare il nostro sito web e di personalizzare le pubblicità. Se non abiliti i cookie, noi utilizzeremo solo cookies di sessione per migliorare la facilità di utilizzo.

Cookie tecnicinon richiedono il consenso, perciò vengono installati automaticamente a seguito dell’accesso al Sito.

Cookie di statisticaVengono utilizzati da terze parti, anche in forma disaggregata, per la gestione di statistiche

Cookie di social networkVengono utilizzati per la condivisione di contenuti sui social network.

Cookie di profilazione pubblicitariaVengono utilizzati per erogare pubblicità basata sugli interessi manifestati attraverso la navigazione in internet.

AltriCookie di terze parti da altri servizi di terze parti che non sono cookie di statistica, social media o pubblicitari.