Sono un maestro del tosare yak.
Ovvero, di quella antica e nobile pratica per cui devi risolvere un problema, che però dipende da un altro, il quale porta a un altro che porta a un altro e a un certo punto ti trovi a depilare uno yak per far funzionare il tuo blog.
Nel mio caso il processo è stato
- mephisto non ha la feature X (che ho dimenticato cosa fosse)
- cavolo dovrei scrivermi il mio blog engine..
- beh posso farlo usando ramaze
- peccato non abbia una gui di amministrazione automatica
- beh posso sempre farla se ho un buon ORM
- ma non mi va di usare ActiveRecord, oltretutto son sempre stato un fan del Data Mapper
- oddio c’è la v0.50 di Og.. no ancora non è rilasciata e non hanno tolto tutti i rescue Object… brrr
- vabè, potrei scrivermene uno io, piccolino, ma ben testato
- cavolo se son brutti sti test..
- ah beh posso usare test/spec
- cavolo sti test fanno schifo..
- e poi non son capace di azzeccare una descrizione..
- ah avessi doctest come in python..
- beh potrei scrivermi il mio..
Ed è a questo punto che ci ritroviamo, circa una settimana dopo, 1400 righe più tardi, e con SLOCCount che per questo progetto stima 4 mesi di lavoro e una spesa di 38.581 dollari, da investire in 0.83 sviluppatori (?).
1400 righe? Ma sei scemo?? diranno ora i miei piccoli lettori.
In verità si, ma non per questo, in quanto solo un quinto di queste sono la libreria, mentre il resto sono i miei test unitari & doctest.
La mia libreria è piccina, se considerate che SLOCCount su ActiveRecord trova 22941 linee di codice
.
Ed il bello è che metà di queste righe sono doctest, che nella mia microimplementazione significa: apri irb, prova cose, copincolla.
Come funziona test/doc
La “libreria” che fa funzionare il tutto è lunga ben 30 righe, perché è mal scritta e scorretta, ma io le voglio bene lo stesso.
L’idea è semplice: ogni operazione in un REPL come irb è composta da due parti: il codice, e il risultato. Se usate irb con l’opzione –simple-prompt si tratta di una cosa come questa:
>> codice()
=> risultato
dunque se le righe sono in un oggetto enumerabile basta usare grep per trovarle e ignorare le altre linee, nelle quali potrete aggiungere utili commenti o, nel mio caso, i modeltest di django ancora da tradurre
Una volta che avete la lista ordinata in coppie input/output basta fare l’eval del primo e confrontrarlo con il secondo. Facile, no?
In realtà no, definendo un metodo o una classe in irb questo non va perché vi trovate con input multilinea, ma io mi limito a ignorare il problema, e i miei test sono abbastanza carini.
Ora: dov’è il posto migliore per mettere i doctest? Nei commenti, potreste dire voi. Concordo in generale, ma in questo caso, visto che le cose da fare sono molte e le righe del modello molto poche, sembrava più conveniente mettere i test nello pseudofile DATA.
Per chi non lo sapesse, ruby mette a disposizione del programmatore una variabile, DATA appunto, che si comporta come un oggetto File, ovvero è possibile leggere le linee, fare seek etc etc.
Il contenuto dell’oggetto non è però un vero file, ma il pezzo del file .rb corrente che sta dopo dopo la keyword __END__.
Sembrava perfetto per il mio scopo. Nessun require strano, nessun problema per fare parsing, codice
& esempi nello stesso file ma separati e dovendo aggiungere una singola linea di codice.
Senonché, e qui viene il problema, DATA non funziona come mi ricordavo io.
Ovvero, la variabile è globale e rappresenta la relativa sezione non del file corrente (cioè di __FILE__) ma dello script in esecuzione (cioè $0 o $PROGRAM_NAME).
Ergo eseguire i miei doctest tramite rcov è impossibile, perché DATA è inizializzato, male, in rcov stesso. Vabè, torniamo al parsing a mano, in fondo basta leggere il file in un array di linee e poi scorrerlo finché non si trova __END__
Però dovrei ridurmi a specificare il nome del file ogni volta che voglio creare un doctest.
Soluzione: filename ||= caller[0][ /^.*?(?=:)/ ].
Lasciando stare la Regexp, significa che usiamo come nome del file quello in cui c’è il codice che crea il DocTest.
Quindi se ho una riga DocTest.new in test/sample.rb non c’è bisogno che usi degli argomenti, perché usando caller posso scoprire automaticamente qual’è il file chiamante (appunto, test/sample.rb.
Ora i miei test funzionano, sono gradevoli e posso usare quelli di django praticamente copincollandoli (come noterete ci son pure gli stessi commenti).
Trentottomila dollari ben spesi!