Integration test in rspec

Sto facendo un integration test alquanto lungo con Rspec. In due parole,
ci sono vari tipi di utenti che si collegano al server per fare varie
cose e l’ordine in cui lo fanno è importante. Per dare un’idea, il
cliente impiega una giornata intera a fare a mano tutto il giro.

Poiché lo stato del database deve mantenersi dall’inizio alla fine (non
uso mock, è una scelta, ed inoltre per l’integrazione non penso abbiano
senso) mi ritrovo con una describe con all’interno un unico blocco it.
Funziona, ma è orribile per tante ragioni che immaginerete facilmente.

La soluzione sarebbe GitHub - LRDesign/rspec-steps: Stepwise operation for rspec che
al posto della describe permette di usare un blocco “steps” con tanti
“it” al suo interno: come spiega su github ‘state is preserved between
examples inside a “steps” block: any DB transactions will not roll back
until the entire sequence has been complete.’

Il guaio è che non funziona con le ultime versioni di rspec e non posso
aspettare che lo sistemino, né ho tempo per studiarmi come funziona e
provare a sistemarlo.

Che sappiate, ci sono alternative rimanendo nel mondo RSpec
(l’investimento ormai è troppo grande per cambiare)? Nel caso servisse,
il client web è Capybara, scelto perché così un giorno potrei fare il
test con il driver Selenium.

Grazie
Paolo

Sposta ogni step in un metodo, dentro al blocco it richiami i vari
metodi.

– Simone

2012/7/9 Paolo M. [email protected]

La soluzione sarebbe GitHub - LRDesign/rspec-steps: Stepwise operation for rspec che
(l’investimento ormai troppo grande per cambiare)? Nel caso servisse,
[email protected]
http://lists.ruby-it.org/mailman/listinfo/ml


Simone C.
Application Developer

Site & Blog: http://www.simonecarletti.com/
LinkedIn: http://linkedin.com/in/weppos
Skype: weppos

E’ circa quello che sto facendo ma non è l’ideale avere un solo blocco
it che gira per minuti senza feedback di avanzamento né feedback su dove
capitano degli errori. Va detto che essendo una sequenza unica, il primo
errore blocca sempre tutto quello che segue quindi forse la separazione
in blocchi è irrilevante.

C’è nessuno che si è trovato a scrivere test di questo genere? Che
approccio avete seguito?

Paolo

Simone C. wrote in post #1068028:

Sposta ogni step in un metodo, dentro al blocco it richiami i vari
metodi.

– Simone

2012/7/9 Paolo M. [email protected]

La soluzione sarebbe GitHub - LRDesign/rspec-steps: Stepwise operation for rspec che
(l’investimento ormai troppo grande per cambiare)? Nel caso servisse,
[email protected]
http://lists.ruby-it.org/mailman/listinfo/ml


Simone C.
Application Developer

Site & Blog: http://www.simonecarletti.com/
LinkedIn: http://linkedin.com/in/weppos
Skype: weppos

Il giorno 09 luglio 2012 22:13, Paolo M.
[email protected]ha scritto:

E’ circa quello che sto facendo ma non l’ideale avere un solo blocco
it che gira per minuti senza feedback di avanzamento n feedback su dove
capitano degli errori. Va detto che essendo una sequenza unica, il primo
errore blocca sempre tutto quello che segue quindi forse la separazione
in blocchi irrilevante.

C’ nessuno che si trovato a scrivere test di questo genere? Che
approccio avete seguito?

La separazione in metodi necessaria per ridurre la duplicazione fra gli
step,
e aumentare il riuso del codice.
In pratica i metodi rappresentano la “descrizione” di cosa la tua app
faccia,
mentre l’implementazione dei metodi rappresenta “come” la fa, in questo
modo,
se cambi un comportamento, devi modificare solo 1 metodo.

Ciao,

Matteo

Ok, quindi continuo con l’approccio che sto seguendo, visto che è quello
che suggerite. Mi sembrava un po’ naif ma evidentemente non c’è di
meglio finché non rifunzionerà rspec-steps. Contraccambio la cortesia di
chi mi ha risposto con qualche dettaglio sull’organizzazione del codice,
e mi scusino quelli tra i lettori che li troveranno ovvi, e qualche
dritta su Capybara, forse meno ovvia.

Ho creato una directory spec/integration con dentro il singolo file con
tutto lo scenario da provare e spec/helpers con dei module da includere
nel test di integrazione. In questi module si possono definire

module Modulo

def self.included(base)
base.before(:all) do … end
base.after(:all) do … end
base.before(:each) do … end
end

def metodo

end

end

con la semantica che immaginerete. Ho un solo test e quindi non mi
riguarda molto, ma è importante eliminare nell’after :all i record che
si creano nel before :all, altrimenti restano nel db tra un test e
l’altro. Ogni modulo cerca di raggruppare i metodi relativi ad un
modello o a un controller, per tenere il codice un po’ organizzato.

Ecco le dritte su Capybara, che mi hanno fatto perdere del tempo.

  1. Il server che sto testando risponde in modo diverso in base
    all’HTTP_HOST nella request. L’HTTP_HOST si imposta in un before :each
    così:

    Capybara.current_session.driver.reset!
    Capybara.default_host = “http://#{@domain}”

L’ho trovato a
http://forrst.com/posts/Testing_Subdomains_in_Capybara-g4M

  1. Mi è anche capitato di dover fare delle chiamate ajax. Tramite
    selenium è facile, ma volendo andare completamente headless, si fanno
    così:

    page.driver.post “http://#{@domain}” + model_path,
    { :key => “value” }, {:xhr => true}
    resp = page.driver.response
    resp.status.should == …
    resp.body.should == …

Si potrebbe non passare tramite page.driver e chiamare direttamente
post, ma si perdono i cookie tra cui quello di sessione e non si sarebbe
più loggati. Riferimenti pro e contro questo approccio:

Paolo

Il giorno 09 luglio 2012 22:13, Paolo M.
[email protected]ha scritto:

E’ circa quello che sto facendo ma non l’ideale avere un solo blocco
it che gira per minuti senza feedback di avanzamento n feedback su dove
capitano degli errori. Va detto che essendo una sequenza unica, il primo
errore blocca sempre tutto quello che segue quindi forse la separazione
in blocchi irrilevante.

C’ nessuno che si trovato a scrivere test di questo genere? Che
approccio avete seguito?

Paolo ma a parte rspec-steps ti trovi bene con un test di integrazione
che
dura (tanti) minuti ?
Ok che di integrazione, quindi lento per definizione.
Ok che deve usare tutto, db compreso (ed usare mock al posto del db
reale
lo rende meno utile).

Ma non riesci a velocizzarlo usando che so, un db locale in process
invece
del db server reale ?
Tipo se usi oracle per il test usare sqlite :slight_smile:

Ciao,
Sergio

La risposta breve è “no, non mi trovo bene ma sono costretto dalle
circostanze”.

Quella più articolata, scritta a più riprese mentre girano i test, è che
l’applicazione era totalmente scoperta perché i test esistenti sono
stati rotti dall’ultimo aggiornamento prima del mio arrivo e gli
sviluppatori per qualche buona ragione (gli do credito) non avevano
avuto tempo per sistemarli. All’inizio avevo dovuto lavorare in
emergenza anch’io ma adesso è imperativo avere un test. Formalmente ci
mette dai 4 ai 5 minuti a girare, ma tra i tempi di startup…

(ecco mi ha detto ora “Finished in 4 minutes 1.81 seconds”, adesso
misuro il tempo reale col cronometro del telefono)

… e alle volte quelli di shutdown
Rspec sometimes takes forever to finish on failed spec example(s) · Issue #601 · rspec/rspec-core · GitHub ci vuole un po’ di più ed
il debug dei test è lento.

Ma ci sono anche dei vantaggi, tipo fare qualche esercizio su duolingo
mentre attendo :slight_smile: Più spesso però vado avanti a scrivere altri pezzi di
test mentre gira quello attuale. Adesso ho quasi finito e dopo il
rilascio in produzione dovrò scrivere anche qualche test più mirato su
modelli e controller per provare in fretta la correttezza delle
modifiche che faccio.

(“Finished in 4 minutes 37.23 seconds” contro 5 minuti e 16 secondi
reali - sorprendente quanto tempo ci voglia a scrivere un paragrafo se
nel frattempo devi googlare cose… ora sistemo un po’ di cose e poi
riprendo la risposta)

Come db sto usando mysql e per inciso sono incappato in

passando alla gemma mysql2
Non ho idea se funzionerebbe anche con sqllite. Eventualmente c’è
qualche versione in RAM di mysql? Mi verrebbe utile pure sul netbook
quando alle volte lo uso per programmare in giro.

(“Finished in 4 minutes 37.45 seconds” vs 5.18, l’overhead pare
costante)

Ho googlato un po’ ed ho scoperto che MySQL ha l’engine MEMORY. In
teoria basta mettere questo codice in un initializer, ma in pratica le
tabelle mi vengono ancora create con InnoDB.

Based on AR 3.0.15 and

http://blog.teksol.info/2008/12/12/mysqls-memory-engine-barely-faster-than-innodbs

module ActiveRecord
module ConnectionAdapters
class MysqlAdapter < AbstractAdapter
def create_table(table_name, options = {}) #:nodoc:
engine = case Rails.env
when “test”
“MEMORY”
else
“InnoDB”
end
super(table_name, options.reverse_merge(:options =>
“ENGINE=#{engine}”))
end
end
end
end

L’autore si lamentava che il suo test era passato da 1.8 s a 1.1 s, che
per lui era irrilevante, ma nel mio caso guadagnarei almeno un minuto.
Indagherò, e farò anche la prova con sqlite.

Paolo

Sergio B. wrote in post #1068358:

Il giorno 09 luglio 2012 22:13, Paolo M.
[email protected]ha scritto:

E’ circa quello che sto facendo ma non l’ideale avere un solo blocco
it che gira per minuti senza feedback di avanzamento n feedback su dove
capitano degli errori. Va detto che essendo una sequenza unica, il primo
errore blocca sempre tutto quello che segue quindi forse la separazione
in blocchi irrilevante.

C’ nessuno che si trovato a scrivere test di questo genere? Che
approccio avete seguito?

Paolo ma a parte rspec-steps ti trovi bene con un test di integrazione
che
dura (tanti) minuti ?
Ok che di integrazione, quindi lento per definizione.
Ok che deve usare tutto, db compreso (ed usare mock al posto del db
reale
lo rende meno utile).

Ma non riesci a velocizzarlo usando che so, un db locale in process
invece
del db server reale ?
Tipo se usi oracle per il test usare sqlite :slight_smile:

Ciao,
Sergio

sqlite non funziona. Apparentemente non è supportato da qualche gemma
che uso, che genera codice sql non compatibile.

Quel codice per l’engine MEMORY di MySQL è ignorato se lo metto in un
initializer o in un environment.rb e la ragione è banale: la classe non
deve essere MysqlAdapter bensì Mysql2Adapter.

Purtroppo ho scoperto due problemi:

  1. per qualche ragione Rails è ancora in development mode quando si
    eseguono le create_table per popolare il database di test a partire da
    db/schema.rb tant’è che logga in development.log quei comandi (è Rails
    3.0.15)

  2. il workaround è impostare l’engine a MEMORY sempre (si potrebbero
    creare degli script per automatizzarlo in fase di test), ma poi ho
    “Mysql2::Error: The used table type doesn’t support BLOB/TEXT columns”
    dato che alcuni modelli prevedono l’attachment di file.

Pare una ragionevole cautela contro il riempirsi la memoria con dei
file, ma sarebbe opportuno avere lo switch “so-quello-che-sto-facendo”
perché in test non correrei di questi rischi.

Conclusione, niente database in memory per questi test. Devono proprio
andare sul disco.

Paolo

Il giorno 12 luglio 2012 11:31, Paolo M.
[email protected]ha scritto:

Purtroppo ho scoperto due problemi:
2) il workaround impostare l’engine a MEMORY sempre (si potrebbero
creare degli script per automatizzarlo in fase di test), ma poi ho
“Mysql2::Error: The used table type doesn’t support BLOB/TEXT columns”
dato che alcuni modelli prevedono l’attachment di file.

Sostituire con uno script nel db/schema.rb data type BLOB/TEXT con data
type base (char, varchar) compatibile con MEMORY engine ?
Salta il data model ?

Ciao,
Sergio

Sergio B. wrote in post #1068405:

Il giorno 12 luglio 2012 11:31, Paolo M.
[email protected]ha scritto:

Purtroppo ho scoperto due problemi:
2) il workaround impostare l’engine a MEMORY sempre (si potrebbero
creare degli script per automatizzarlo in fase di test), ma poi ho
“Mysql2::Error: The used table type doesn’t support BLOB/TEXT columns”
dato che alcuni modelli prevedono l’attachment di file.

Sostituire con uno script nel db/schema.rb data type BLOB/TEXT con data
type base (char, varchar) compatibile con MEMORY engine ?
Salta il data model ?

Dovrei indagare. Di sicuro prima ho scritto una sciocchezza perché dei
file memorizzo solo il path in dei varchar (ma mi viene in mente che un
nome di file lungo potrebbe sforare…). Ho però dei campi text che
potrebbero davvero dover contenere dei testi lunghi, quindi non si può
fare così.

Però ho due buone notizie. Una è che ho finito il primo test di
integrazione completo “Finished in 6 minutes 16.88 seconds” e la seconda
è che ho trovato un’altra soluzione: bigdbahead.com is almost here!

Bisogna però installare una versione diversa di mysql, su linux
probabilmente con

$ sudo apt-get install mysql-cluster-server-5.1

e lo proverò un giorno in una virtual machine. Documentazione a
http://www.intertad.info/server/mysql-5.1/mysql-cluster.html

Paolo