Forum: Italian Ruby user group Integration test in rspec

Posted by Paolo Montrasio (pmontrasio)
on 2012-07-09 19:50
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 https://github.com/LRDesign/rspec-steps 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
Posted by Simone Carletti (weppos)
on 2012-07-09 19:53
(Received via mailing list)
Sposta ogni step in un metodo, dentro al blocco it richiami i vari 
metodi.

-- Simone


2012/7/9 Paolo Montrasio <paolo@paolomontrasio.com>

> La soluzione sarebbe https://github.com/LRDesign/rspec-steps che
> (l'investimento ormai  troppo grande per cambiare)? Nel caso servisse,
> Ml@lists.ruby-it.org
> http://lists.ruby-it.org/mailman/listinfo/ml
>



--
Simone Carletti
Application Developer

Site & Blog: http://www.simonecarletti.com/
LinkedIn: http://linkedin.com/in/weppos
Skype: weppos
Posted by Paolo Montrasio (pmontrasio)
on 2012-07-09 22:13
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 Carletti wrote in post #1068028:
> Sposta ogni step in un metodo, dentro al blocco it richiami i vari
> metodi.
>
> -- Simone
>
>
> 2012/7/9 Paolo Montrasio <paolo@paolomontrasio.com>
>
>> La soluzione sarebbe https://github.com/LRDesign/rspec-steps che
>> (l'investimento ormai  troppo grande per cambiare)? Nel caso servisse,
>> Ml@lists.ruby-it.org
>> http://lists.ruby-it.org/mailman/listinfo/ml
>>
>
>
>
> --
> Simone Carletti
> Application Developer
>
> Site & Blog: http://www.simonecarletti.com/
> LinkedIn: http://linkedin.com/in/weppos
> Skype: weppos
Posted by Matteo Collina (Guest)
on 2012-07-10 09:14
(Received via mailing list)
Il giorno 09 luglio 2012 22:13, Paolo Montrasio
<paolo@paolomontrasio.com>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
Posted by Paolo Montrasio (pmontrasio)
on 2012-07-10 15:19
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

2) 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:

* http://suffix.be/blog/capybara-post-requests
* http://www.elabs.se/blog/34-capybara-and-testing-apis
* http://www.sinatrarb.com/testing.html



Paolo
Posted by Sergio Berisso (Guest)
on 2012-07-12 07:50
(Received via mailing list)
Il giorno 09 luglio 2012 22:13, Paolo Montrasio
<paolo@paolomontrasio.com>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 :)

Ciao,
Sergio
Posted by Paolo Montrasio (pmontrasio)
on 2012-07-12 10:44
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
https://github.com/rspec/rspec-core/issues/601 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 :-) 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
http://stackoverflow.com/questions/5705185/switch-...
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-e...

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 Berisso wrote in post #1068358:
> Il giorno 09 luglio 2012 22:13, Paolo Montrasio
> <paolo@paolomontrasio.com>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 :)
>
> Ciao,
> Sergio
Posted by Paolo Montrasio (pmontrasio)
on 2012-07-12 11:31
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
Posted by Sergio Berisso (Guest)
on 2012-07-12 11:41
(Received via mailing list)
Il giorno 12 luglio 2012 11:31, Paolo Montrasio
<paolo@paolomontrasio.com>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
Posted by Paolo Montrasio (pmontrasio)
on 2012-07-12 12:11
Sergio Berisso wrote in post #1068405:
> Il giorno 12 luglio 2012 11:31, Paolo Montrasio
> <paolo@paolomontrasio.com>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: http://www.bigdbahead.com/?p=183

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
Please log in before posting. Registration is free and takes only a minute.
Existing account (Switch to SSL-encrypted connection)
NEW: Do you have a Google/GoogleMail or Yahoo account? No registration required!
Log in with Google account | Log in with Yahoo account
No account? Register here.