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

  1. mephisto non ha la feature X (che ho dimenticato cosa fosse)
  2. cavolo dovrei scrivermi il mio blog engine..
  3. beh posso farlo usando ramaze
  4. peccato non abbia una gui di amministrazione automatica
  5. beh posso sempre farla se ho un buon ORM
  6. ma non mi va di usare ActiveRecord, oltretutto son sempre stato un fan del Data Mapper
  7. oddio c’è la v0.50 di Og.. no ancora non è rilasciata e non hanno tolto tutti i rescue Object… brrr
  8. vabè, potrei scrivermene uno io, piccolino, ma ben testato
  9. cavolo se son brutti sti test..
  10. ah beh posso usare test/spec
  11. cavolo sti test fanno schifo..
  12. e poi non son capace di azzeccare una descrizione..
  13. ah avessi doctest come in python..
  14. 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!