Cosa è presente tra i tuoi strumenti di sviluppo?
Questo articolo tratta approfonditamente alcuni strumenti per il metodo di sviluppo Test-First. Se non conosci il metodo Test-First e vorresti approfondire l’argomento puoi consultare le seguenti risorse:
- Xp Magazine
- il mio articolo su IBM Developerworks
- il mio precedente articolo su ZenTest
Mi piace scrivere codice Test-First, perché mi sembra di avere più sicurezza su ciò che ho scritto. Usando il Test-First posso completare velocemente il lavoro e posso rifattorizzarlo più facilmente con un design migliore. Non è male il fatto che Ruby fornisca utili strumenti secondo i principi del Test-First o che sia disponibile per Ruby qualche buono strumento per il Test-First.
Il mio toolbox per il Test-First include Test::Unit, rake, rcov, autotest e unit_diff. I primi due dovrebbero essere abbastanza familiari alla maggior parte degli hacker Ruby. Se non ne hai mai sentito parlare, il Test::Unit è ben documentato sul libro Pick Axe e su www.ruby-doc.org. Puoi leggere qualcosa in più su rake all’interno del mio articolo su IBM Developerworks e sull’articolo di Martin Fowler.
Se non stai già usando Test::Unit e rake, prenditi un po’ di tempo per imparare questi ottimi strumenti. Test::Unit è distribuito con Ruby e rake è disponibile come rubygem (gemma ruby) su RubyForge.
Come per gli altri strumenti all’interno del mio toolbox, rcov è uno strumento di analisi di copertura del codice. Quando vengono eseguiti un insieme di unit test, esso genera un’analisi di copertura del codice. rcov è disponibile su eigenclass.org. Puoi generare un output in HTML — puoi vedere un esempio qui — o un output ASCII, una forma troncata di quello che è mostrato sotto. rcov lavora velocemente; eseguire un programma con rcov è solo due o tre volte più lento che lanciare il programma normalmente. In più, rcov produce risultati utili e funzionali.
1 class MockDB | 2 2 | 0 3 def exec | 11 4 case query.split[3] | 5 5 when 'zero' | 5 6 num = 0 | 1 7 when 'one' | 4 8 num = 1 | 1 9 else | 0 10 num = 2 | 3 11 end | 0 12 | 0 13 yield [num] | 5 14 | 0 15 end | 0 16 | 0 17 end | 0
Per lanciare rcov su un insieme di test:
1 $ rcov test/test_hostname
Utili opzioni da riga di comando sono:
- -t: genera risultati in testo semplice
- -T: genera risultati in testo decorato
- -p: genera risultati in un formato personalizzato
- -x: esclude file; accetta una lista di espressioni regolari separate da virgola
- –no-html: non crea file HTML
Se scegli di generare un risultato testuale, può essere utile scrivere i risultati su un file. Oppure potresti utilizzare tee per scrivere l’output su un file. I risultati di uscita di rcov possono essere molto lunghi.
Sebbene in genere gli strumenti di copertura siano utilizzati per mostrare dove è necessaria la scrittura di più test, recentemente rcov mi ha portato a rifattorizzare il codice. Ho scritto per un paio di ore del codice Test-First e ho implementato un certo numero di controlli. Ho deciso di fermarmi nello per verificare quanto fosse buona la copertura del codice. Mi aspettavo che fosse al 100%, ma a volte è bello vedere che mi stavo sbagliando. Così, sono rimasto sconvolto nel vedere una barra rossa alla fine di quella verde. Qualcosa non veniva testato!
Ho esaminato il codice e le mie asserzioni. Potevo vedere dove stessi testando il caso che falliva, ma rcov non mi credeva. Esso sottolineava che un controllo che avevo implementato dopo quello non coperto duplicava il test, così i miei fallimenti venivano intercettati prima di arrivare lì. Dovevo o dividere il mio metodo di controllo o elimare il codice non utilizzato. Grazie a rcov, il mio codice si è ridotto di un metodo.
La versione corrente di rcov ha un piccolo bug che dovrai eliminare. Non controlla le continuazioni di linea dopo un “and” o un “or”. E’ necessaria la correzione di una linea all’interno del codice di rcov e Mauricio lo correggerà nella prossima versione.
Gli altri strumenti del mio toolbox, come autotest e unit_diff, sono distribuiti con ZenTest, che è disponibile come rubygem. Entrambe sono stati realizzati per facilitare il testing all’interno delle routine.
Con il tuo codice in ./lib e i tuoi test in ./test, autotest divorerà i file di test, li eseguirà e mostrerà i risultati. Costruisce una mappa tra i file di test e quelli del programma, classi e metodi. Ogni volta che salverai un file presente nella mappa o ne creerai uno che si inserirà nella lista, autotest rilancerà i test. Ogni volta che un test fallisce, autotest entra in un ciclo ristretto, eseguendo solo i test che falliscono. Questo consente di individuare subito il codice che fallisce e correggerlo immediatamente.
Ho impiegato un paio di ore di lavoro con autotest prima di entrare nel meccanismo. Le prime cose che ho fatto sono stati piccoli lavori — Ho aggiornato alcuni test per Ruby 1.8.4 — così autotest non ha dovuto ciclare sull’intero codice, esaminare i cambiamenti e eseguire i casi di test. autotest riesce comunque a farlo; premendo una volta Ctrl-c rilancia i test immediatamente. Premendo due volte la combinazione di tasti si termina autotest.
Ho anche visto che ad autotest non piace quanto i test falliscono. Quando sto scrivendo codice Test-First, all’inizio di un ciclo di sviluppo, spesso richiedo un file che ancora non esiste. Nel normale sviluppo inserisco errori di scrittura o di sintassi. Ognuno di questi errori provocano in autotest la generazione del seguente errore Ruby:
1 Test::Unit died, you did a really bad thing, retrying in 10
Sebbene non sia la cosa migliore del mondo, è un modo per capire i nostri errori e riportarci sulla strada giusta.
Un ultimo appunto: autotest non gradisce i file generati automaticamente da Emacs durante l’autosalvataggio e interrompe la propria esecuzione ogni volta che ne incontra uno. Una semplice patch è stata sottomessa al progetto RubyForge ZenTest. Una nuova versione è all’orizzonte e questo problema dovrebbe essere risolto.
unit_diff è un altro piccolo programma che diventa velocemente di inestimabile valore. Esso mette le porzioni di output “attese” e quelle “ricevute” dal fallimento di un’asserzione in diff, riducendo pertanto il lungo blocco di testo in qualcosa di molto più gestibile. Eric Hodel, autore di unit_diff, afferma che il programma è stato scritto per aiutare lo sviluppo ParseTree, dove ogni asserzione che fallisce produce molte schermate di risultati densi e difficili da leggere. unit_diff trasforma questo incubo in due o tre linee che mostrano l’esatto errore. In più, ho trovato unit_diff di impagabile valore lavorando su output XML.
In più unit_diff è facile da eseguire. Reindirizza semplicemente l’output verso di esso in questo modo:
1 $ ruby test/test_hostname |unit_diff
Tutti gli errori che trova li cattura e li visualizza correttamente.
Spero che vi sia piaciuta questa breve digressione sugli strumenti di sviluppo Test-First per Ruby. Tornerò presto per parlare di Ruby. Se vorreste che approfondissi l’argomento sentiti libero di lasciare un commento sotto.

