non è un segreto, sono
test-infected. questo significa che prima di iniziare a scrivere del codice, il mio istinto (o malattia) mi porta a partire da un test automatico. ma non è solo questo: l'approcio infatti mi si rivela utile in ogni situazione in cui ci sia da scoprire come fare qualcosa di nuovo.
Test di esplorazione
questa settimana, ad esempio, ho esplorato una libreria di serializzazione/deserializzazione XML, partendo da un test unitario: dato un frammento XML, mi aspetto un oggetto deserializzato di un certo tipo e in certo stato, e viceversa. aiutato da qualche stampa a console di corredo, mi sono fatto guidare dalle barre verdi: si parte con un caso semplice, un oggetto vuoto, poi aggiungo una collezione di oggetti, infine decido un mapping custom tra attributi e classi. in due pomodori di pair-programming siamo riusciti a farci una chiara idea delle funzionalità offerte, e devo dire che anche la mia compagna di pair, nuova alla metodologia, è rimasta davvero soddisfatta del risultato ottenuto in così poco tempo.
nel tempo libero, ho anche iniziato studiare un nuovo linguaggio dinamico, per esplorare come rendere espressiva una DSL per test di accettazione che voglio scrivere. la prima cosa che ho cercato nella documentazione del linguaggio è come scrivere un test automatico. sono partito scrivendo qualche semplice asserzione sullo stato del sistema dopo aver eseguito un breve frammento di DSL. la prima implementazione poi si è occupata di far passare il test, senza usare nessuna feature del linguaggio: solo costruttori, chiamate a metodi e passaggio di parametri. dopo la prima barra verde, ho potuto iniziare a rifattorizzare il frammento introducendo blocchi e named-parameters. prossimo passo saranno le extensions e i builder, e magari poi imparerò qualcosa pure di meta-programming.
[ è lo stesso approcio che ho seguito qualche anno fa quando ho voluto imparare le nuove feature di Java, venendo da .NET: con una manciata di test unitari ho capito il funzionamento di generics, varargs e foreach. niente di così strano, ho seplicemente tradotto in asserzioni quello che, su ogni manuale di programmazione, viene tipicamente messo in una stampa a console dentro un main. in questo modo però ho reso ripetibili e deterministici gli esiti delle mie esplorazioni. ]
Piccoli test crescono
questo venerdì la retrospettiva di fine iterazione, nel progetto in cui sto lavorando sia come mentore che come sviluppatore, mi ha fatto riflettere e mi sono accorto di quanto incisivo, seppur nascosto, sia stato l'apporto che i miei colleghi ed io siamo riusciti a dare al team che stiamo seguendo: nell'arco di qualche mese siamo passati da "test automatici, questi sconosciuti" a una sana discussione sull'esigenza di introdurre, in aggiunta a test unitari e di integrazione, una nuova famiglia di test funzionali; a come accelerare l'esecuzione dell'intera suite di test durante la fase di build; a come automatizzare la creazione di una base di dati con dati di test reali; e così via.
in tutto questo, mi reso conto che uno dei contributi più validi che ho potuto dare al team è stato aiutare a trovare un modo per testare nuove parti del sistema. tipicamente, mi accade di affiancarmi a sviluppatori che sono un po' scettici quando dico loro "partiamo scrivendo un test". ma un po' alla volta, i risultati sono arrivati.
uno dei primi task per il quale mi sono offerto è stato introdurre un modo per testare le view della webapp, scritte con un template engine che non conoscevo. sono partito con uno spike per capire la sintassi del linguaggio di markup dei template e come eseguire il rendering di una view. uno sguardo alla documentazione on-line, e via con il primo test: dato un file con "Hello, world!", avvio l'engine, processo il file, e mi attendo come risultato la stringa con il canonico saluto a tutto il mondo. alla prima barra verde, certo non immediata, la strada era ormai avviata, e via quindi con bindind di variabili, espressioni condizionali, cicli e tutto il resto. abbiamo dovuto accettare qualche compromesso (come bypassare il motore di localizzazione), ma il risultato del task è stata una comoda BaseTestForTemplate. e così, dopo un paio di mesi, la codebase ora contiene una quarantina di preziosissime classi di test, una per ciascuna singola view, scritte da tutto il team.
il vantaggio più grosso che questo ha avuto, per me, è stata la tranquillità con cui, da allora, ho potuto mostrare ad alcuni sviluppatori come svuotare di
logiche applicative i template delle viste, spostandole sugli oggetti di dominio e unit-testandole separatamente. il
ciclo di feedback, per sapere se una certa fuzionalità fosse corretta è passato da
decine di minuti - necessari per fare un nuovo deploy, inserire i dati necessari e navigare l'applicazione - a
qualche minuto, giusto il tempo di aggiungere qualche setup e una nuova asserzione.
il passo successivo è stato provare a rimuovere del tutto la necessità di fare deploy e verifiche manuali del funzionamento dell'applicazione. un primo risultato c'è stato quando, grazie al supporto di opportune librerie, ho introdotto un test di integrazione HTTP in grado di caricare in memoria il contesto della webapp e verificare che tutto, dalla view all'accesso ai dati, fosse interconnesso correttamente. più volte infatti capitava che, involontariamente, venisse introdotto qualche errore nella configurazione XML dell'applicazione (in particolare, la parte realtiva al framework di IoC), e questo si rendeva visibile solo provando ad avviare l'applicazione in locale. non è stato facile, ma il risultato è stato soddisfacente e ora siamo in grado di accorgerci durante la build di eventuali errori di mapping o configurazione.
una cosa che noto spesso è che molti sviluppatori non riconoscono di essere loro stessi abituati a farsi guidare dal feedback (che è il valore che guida il TDD). in una sessione di pair-programming mi sono affiancato come mentore ad un ragazzo che doveva realizzare una stored procedure per selezionare i risultati di una ricerca. il suo approcio consisteva nello scrivere il codice SQL, compilare la stored-procedure, inserire alcuni dati di prova ed eseguire una ricerca, e verificare che i dati ottenuti fossero corretti. ovviamente, questa procedura ha lo svantaggio di essere lunga e ripetitiva, quindi soggetta a possibili errori (sopratutto quando ti ritrovi a fare continuamente le stesse operazioni e sei stressato). la mia prima domanda è stata "perchè non proviamo a scrivere un test?".
ho scarsissima esperienza con le stored-procedure, e non conosco affatto la sintassi proprietaria SQL del DBMS che stiamo usando. mi serviva quindi "un punto fermo" su cui fare affidamento, e un test si prestava benissimo. così, con non troppa fatica, abbiamo scritto un test di integrazione che, dopo aver aperto una connessione al database, esegue una chiamata alla stored-procedure. barra rossa: la stored-procedure non esiste. bene! significa che ora ci possiamo dedicare a scrivere il codice SQL. dopo pochi minuti, arriva la prima barra verde. passo successivo è l'inserimento di alcuni dati di prova, con una coppia di INSERT/DELETE nel setup e teardown del test. nuova asserzione e nuova barra rossa. un po' di lavoro con SQL ed ecco che la barra torna verde. e sopratutto inizio a vedere soddisfatto il mio compagno di pair.
qualche giorno dopo, insieme ad un altro sviluppatore, è arrivato l'ultimo tassello: l'introduzione di transazionalità. ci siamo arrivati in modo incrementale, anche perchè ha richiesto qualche modifica per permettere a test e applicazione di condividere la stessa transazione (e devo dire che in questo caso il framework di IoC usato è stato davvero utile).
se guardo indietro quindi, vedo come in questi primi mesi sul progetto il mio più grande contributo sia stato aiutare il team a scoprire come testare le parti di sistema più complicate. è per me una grossa soddisfazione veder crescere, di pari passo con l'applicazione, la suite di test automatici, a più livelli. oppure sentirmi chiedere "mi dai una mano a testare questa parte?".
o ancora, sentire allo standup-meeting che uno sviluppatore ha creato una classe per testare, in modo automatico, equals e hashcode. sì, decisamente sono test-infected... e pure contagioso!
ah, e la validazione javascript? ok, questa la racconto un'altra volta!
ciao.
-papo-