ruby, oop April 26, 2006 6:19 am (Save post)
Questo post si basa sul piccolo lavoro fatto la volta scorsa per creare special case nei metodi .
Avendo a disposizione quello strumento posso affrontare una delle Grandi Domande: mi conviene fare refactoring per rendere il codice più facilmente testabile se posso evitarlo?
Nel dettaglio: ho uno script che prende in automatico dei dati da risorse su web, e poi fa un grafico con i cambiamenti, insomma una scopiazzatura di EhList, fatta per giocare con le api di Yahoo! SiteExplorer in ruby
Poiché i servizi sono tutti differenti esiste un metodo specifico per accedere ad ognuno di essi, catturato nell’esistenza di una classe Provider e di varie sottoclassi, ognuna con una descrizione, un metodo specifico per recuperare le informazioni remote ed eventuali altre cose.
Un esempio di provider è:
class YahooProvider < Provider
DESC=\"Yahoo SE inbound links\"
APPID=\"myAppId\"
URL=\"url yahoo SE?query=%s&appid=%s\"
def initialize(name)
text=open(URL%[name,APPID]).read
@result=Mapping.xml2obj(text)
end
def results_size
@result.xmlattr_totalResultsAvailable
end
end
se state imprecando contro di me perché non chiudo la connessione aperta con open() potete calmarvi, lo script completa le operazioni in meno di un minuto e si chiude, il che fa si che le risorse vengano comunque recuperate.
Ora: come testare questo provider? In particolare, come testare l’operazione di parsing dell’xml?
La cosa più ovvia è passargli dei dati preconfezionati, magari presi da un’esecuzione precedente dello script, e verificare che li interpreti correttamente.
Solo che è il metodo initialize ad eseguire sia la letttura dei dati che il parsing.
La soluzione che permette di testare meglio tutto ciò è di spostare queste funzionalità in un altro posto per poterle testare in isolamento, con una cosa come questa:
def read_data()
open(URL%[name,APPID]).read
end
def parse(xml)
Mapping.xml2obj(xml)
end
def initialize(name)
testo=read_data()
@result= parse(testo)
end
Se è vero che questo approccio introduce una certa flessibilità è anche vero che si tratta di una flessibilità non necessaria, o meglio di una fattorizzazione del codice in più parti che però non è utile in quanto non userò mai una delle funzionalità separatamente dall’altra.
Insomma, sto facendo una cosa di cui non ho bisogno solo per far felici i test, che non è una cosa terribile, ma la eviterei.
Ma, se siete arrivati a leggere fin qui probabilmente sapete già cosa possiamo fare in questo caso:
require 'stringio' special Kernel, \"open\" do StringIO.new(\"xml di prova\") end assert_equal valore_atteso, YahooProvider.new(url).results_size
è un hack, e non è molto nella filosofia di come dovrebbero essere i test probabilmente..
ma funziona e non aggiunge complicazioni al codice originale.
La domanda è: cosa è giusto fare? E’ giusto perseguire un approccio alla scrittura di test che è tipico in altri ambienti anche se in questo caso ne posso fare a meno sfruttando le potenzialità di ruby?
Infine, se vi state chiedendo che cosa significhi la frase relativa ad open() nel titolo, sappiate che stavo pensando che sarebbe molto carino poter usare qualcosa come
open(”mock://foo”), ed in realtà è possibile farlo con open-uri.rb, magari in futuro ne parlerò. Però non è builtin, uffa

