Test Driven Development vs Test Automatici
Dopo aver introdotto il Test Diven Development:
Vorrei chiarire bene un concetto: lavorare con TDD non è la stessa cosa di scrivere codice corredato da test automatici anche con la copertura del 100%.
Chiariamoci: non sto dicendo che avere un pezzo di codice completamente testato non abbia un valore... anzi!
Io vorrei far capire che TDD è un metodo di scrittura del codice e non di scrittura dei test. La differenza è molto importante.
Facciamo un confronto tra la soluzione descritta nell'articolo su TDD e la seguente: scrivo un profiler , ovviamente il miglior profiler del mondo, e poi scrivo i 1000 casi di test che servono a verificarne e certificarne il funzionamento e che mi danno la copertura del 100%... magari mi baso anche su strumenti eccezionali come ad esempio:
...ad ogni bug che trovo, correggo il codice ed aggiungo un caso di test... Alla fine avrò sicuramente un pezzo di codice completamente testato e quindi molto solido! Questo però è diverso da scrivere un test e poi scrivere il codice che lo verifica e fare refactoring. Vediamo il perchè...
Il punto di partenza è diverso!
Nel caso di Test Automatici con 100% di copertura parto da una soluzione che ho già in mente (o addirittura già scritta), magari scrivendo il codice noto qualche punto di miglioramento, e faccio un po' di refactoring... ma fondamanetalmente l'architettura ed il disegno della soluzione sono quelli che ho pensato all'inizio...
Nel caso TDD, invece, la soluzione intesa come disegno complessivo ed architettura emerge durante la scrittura del codice e garantisce la massima qualità in funzione delle invocazioni (leggi test) che ne farò.
Quindi il percorso, quindi, è esattamente l'inverso e la qualità risultante è decisamente superiore dal punto di vista dell'evolvibilità anche se dal punto di vista della copertura dei test il primo approccio potrebbe anche essere migliore.
Il codice deve essere semplice da evolvere!
Il perchè è legato al fatto che il codice scritto con TDD non è legato ad alcun vincolo se non quello dei test ed è intrinsecamente capace di evolvere poichè ad ogni test ha subito un refactoring che ne ha ripulito l'architettura e garantito la semplice manutenibilità... Perchè ricordatevi che il vostro codice ha valore se è semplice da modificare:
Come esempio basterebbe misurare la complessità (numero ciclomatico) del codice che già da tempo sappiamo essere un buon predittore della qualità del codice:
La soluzione scritta a partire da un architettura data tende ad essere molto più rigida rispetto a quella prodotta tramite TDD in cui a seguito di ogni test si esegue il refactoring (provare per credere). Ad esempio verificate rispetto a quantomscritto qui:
Inoltre, vale la pena di notare che nel nostro esempio, siamo partiti dall'idea secondo cui eravamo in grado di disegnare una soluzione. Esistono, però, problemi, che sono così complessi (o così difficili da formalizzare) che a priori non è possibile mettere su carta una soluzione e poter "garantire" che sia la migliore. In questi casi l'approccio TDD è decisamente il megliore!
In sintesi
In sintesi, TDD e Test Automatici quindi applicano 2 approcci simmetrici usando gli stessi strumenti (i test) e conducono a due tipologie di soluzioni completamente diverse.
L'aspetto fondamentale da ricordare è che se uso TDD parto dai test, se uso i Test Automatici parto dal codice. Quindi AT è applicabile anche a codice che non è nato con alcun tipo di test. TDD no!
E' evidente che, avendo un test di copertura del 100%, potrei anche iniziare un processo di refactoring in maniera controllata, di solito, però, il refactoring a posteriori porta a soluzioni che continuano a risentire del disegno originario e non possono fruire dei vantaggi di tutta la storia del vostro software.
...e i Design Pattern
L'ultima osservazione riguardo all'articolo orginale su TDD riguarda Design Pattern, OOD e best practice: la maggior parte degli sviluppatori li conosce, li ha studiati e li sa applicare abbastanza bene, rimane, però, il mistero legato al fatto che il codice è comunque di scarsa qualità.
In oltre 20 anni di analisi di applicazioni grandi e piccole, confermo di aver visto molti casi in cui gli sviluppatori hanno provato ad applicare tutto il corredo teorico a loro disposizione. Spesso, purtroppo, i risultati non sono stati all'altezza.
Il problema, a mio avviso, è proprio legato a come sono applicati questi principi: se li applico prima (a priori) di scrivere il codice e quindi in fase di analisi e design (perchè ho già in mente la soluzione che ritengo migliore) spesso risultano essere un vincolo per l'evolvibilità del codice, quando proprio non sono applicati male (per cui alla lunga diventano antipattern). Se li applico, invece, a posteriori come fase di refactoring, risultano essere uno strumento di pulizia ed organizzazione del codice (eliminano le duplicazioni) che non impattano, ma agevolano, l'evoluzione dello stesso...
Anche in questo caso, l'approccio TDD è vincente rispetto a quello tradizionale.
La soluzione in questi casi, secondo me prevede:
- un processo di sviluppo adeguato (ad esempio TDD)
- un meccanismo di "validazione" del codice scritto (Code Review o Pair Programming)
- un CTO (non un team leader) che possegga un bagaglio di strumenti tali da poter mettere in campo la miglior soluzione in funzione degli obiettivi del progetto