2010-09-13 11 views
8

Sto facendo uno sforzo concertato per avvolgere la mia mente attorno a Rspec al fine di passare a un altro schema di sviluppo TDD/BDD. Tuttavia, sono molto lontana e alle prese con alcuni dei fondamenti:Quando e quando non eseguire lo stub/simulazione di un test

Come, quando esattamente dovrei usare mock/stub e quando non dovrei?

Prendiamo ad esempio questo scenario: Ho un modello che Sitehas_many :blogs e il modello has_many :articlesBlog. Nel mio modello Site ho un filtro di richiamata che crea un set predefinito di blog e articoli per ogni nuovo sito. Voglio testare il codice, quindi ecco qui:

describe Site, "when created" do 

    include SiteSpecHelper 

    before(:each) do 
    @site = Site.create valid_site_attributes 
    end 

    it "should have 2 blogs" do 
    @site.should have(2).blogs 
    end 

    it "should have 1 main blog article" do 
    @site.blogs.find_by_slug("main").should have(1).articles 
    end 

    it "should have 2 secondary blog articles" do 
    @site.blogs.find_by_slug("secondary").should have(2).articles 
    end 

end 

Ora, se corro quel test, tutto passa. Tuttavia, è anche piuttosto lento in quanto sta creando un nuovo sito, due nuovi blog e tre nuovi articoli - per ogni singolo test! Quindi mi chiedo, questo è un buon candidato per l'utilizzo di stub? Diamo un andare:

describe Site, "when created" do 

    include SiteSpecHelper 

    before(:each) do 
    site = Site.new 
    @blog = Blog.new 
    @article = Article.new 
    Site.stub!(:create).and_return(site) 
    Blog.stub!(:create).and_return(@blog) 
    Article.stub!(:create).and_return(@article) 
    @site = Site.create valid_site_attributes 
    end 

    it "should have 2 blogs" do 
    @site.stub!(:blogs).and_return([@blog, @blog]) 
    @site.should have(2).blogs 
    end 

    it "should have 1 main blog article" do 
    @blog.stub!(:articles).and_return([@article]) 
    @site.stub_chain(:blogs, :find_by_slug).with("main").and_return(@blog) 
    @site.blogs.find_by_slug("main").should have(1).articles 
    end 

    it "should have 2 secondary blog articles" do 
    @blog.stub!(:articles).and_return([@article, @article]) 
    @site.stub_chain(:blogs, :find_by_slug).with("secondary").and_return(@blog) 
    @site.blogs.find_by_slug("secondary").should have(2).articles 
    end 

end 

Ora tutti i test ancora passare, e le cose sono un po 'più veloce anche. Ma ho raddoppiato la lunghezza dei miei test e l'intero esercizio mi sembra semplicemente inutile, perché non sto più testando il mio codice, sto solo testando i miei test.

Ora, o ho completamente perso il punto di deride/stub, o mi sto avvicinando che fondamentalmente sbagliato, ma sto sperando che qualcuno potrebbe essere in grado, a:

  • Migliorare me test di cui sopra quindi usa stub o mock in un modo che mette alla prova il mio codice, piuttosto che i miei test.
  • Oppure, dimmi se dovrei anche usare gli stub qui - o se in realtà questo è completamente inutile e dovrei scrivere questi modelli nel database di test.

risposta

2

Ma, ho raddoppiato la lunghezza dei miei test e l'intero esercizio colpisce solo me come assolutamente inutile, perché non ho più sto testando il mio codice, sto solo testando le mie prove.

Questa è la chiave qui. I test che non testano il tuo codice non sono utili. Se è possibile modificare negativamente il codice che i test dovrebbero essere testati, e i test non falliscono, non ne vale la pena.

Come regola generale, non mi piace prendere in giro/fermare qualcosa a meno che non sia necessario. Ad esempio, quando sto scrivendo un test del controller e voglio assicurarmi che l'azione appropriata si verifichi quando un record non riesce a salvare, trovo più facile fermare il metodo save dell'oggetto per restituire false, piuttosto che elaborare con cura i parametri solo quindi per essere sicuro che un modello non riesca a salvare.

Un altro esempio è un helper chiamato admin? che restituisce true o false in base al fatto che l'utente attualmente connesso sia o meno un amministratore. Non volevo passare per falsificare un login utente, quindi ho fatto questo:

# helper 
def admin? 
    unless current_user.nil? 
    return current_user.is_admin? 
    else 
    return false 
    end 
end 

# spec 
describe "#admin?" do 
    it "should return false if no user is logged in" do 
    stubs(:current_user).returns(nil) 
    admin?.should be_false 
    end 

    it "should return false if the current user is not an admin" do 
    stubs(:current_user).returns(mock(:is_admin? => false)) 
    admin?.should be_false 
    end 

    it "should return true if the current user is an admin" do 
    stubs(:current_user).returns(mock(:is_admin? => true)) 
    admin?.should be_true 
    end 
end 

Come una via di mezzo, si potrebbe voler esaminare Shoulda. In questo modo puoi semplicemente assicurarti che i tuoi modelli abbiano un'associazione definita, e fidati che Rails sia ben testato abbastanza che l'associazione "funzionerà" senza che tu debba creare un modello associato e poi contarlo.

Ho un modello chiamato Member che praticamente tutto nella mia app è legato alla. Ha 10 associazioni definite. Ho potuto testare ciascuna di queste associazioni, o potrei solo fare questo:

it { should have_many(:achievements).through(:completed_achievements) } 
it { should have_many(:attendees).dependent(:destroy) } 
it { should have_many(:completed_achievements).dependent(:destroy) } 
it { should have_many(:loots).dependent(:nullify) } 
it { should have_one(:last_loot) } 
it { should have_many(:punishments).dependent(:destroy) } 
it { should have_many(:raids).through(:attendees) } 
it { should belong_to(:rank) } 
it { should belong_to(:user) } 
it { should have_many(:wishlists).dependent(:destroy) } 
+0

Grazie, che è una risposta utile. :) Grazie – aaronrussell

1

Questo è esattamente il motivo per cui io uso stub/deride molto raramente (in realtà solo quando ho intenzione di essere colpire un webservice esterni). Il tempo risparmiato non vale la complessità aggiunta.

Ci sono modi migliori per accelerare i tempi di test, e Nick Gauthier fa una bella chiacchierata su alcuni di essi - vedere lo video e lo slides.

Inoltre, penso che una buona opzione sia provare un database sqlite in memoria per le esecuzioni di test. Questo dovrebbe ridurre il tempo del tuo database di un bel po 'non dovendo colpire il disco per tutto. Non l'ho provato da solo, però (uso principalmente MongoDB, che ha lo stesso vantaggio), quindi calpesti attentamente. Here's un post del blog abbastanza recente su di esso.

+0

per la tua risposta e link. Guardando il video ora. – aaronrussell

1

Io non sono così sicuro che con accordo sugli altri. Il vero problema (come vedo io) qui, è che stai testando più parti di comportamento interessante con gli stessi test (il comportamento di ricerca e la creazione). Per ragioni sul perché questo è male, vedi questo discorso: http://www.infoq.com/presentations/integration-tests-scam. Sto assumendo per il resto di questa risposta che vuoi testare che la creazione è ciò che vuoi testare.

test

isolazionisti sembrano spesso ingombrante, ma questo è spesso perché hanno lezioni di progettazione per insegnare. Qui di seguito sono alcune cose di base che posso vedere da questo (anche se senza vedere il codice di produzione, non posso fare troppo bene).

Per i principianti, per interrogare il progetto, avere lo Site aggiungere articoli a un blog ha senso? Che dire di un metodo di classe su Blog chiamato qualcosa come Blog.with_one_article. Ciò significa che tutto ciò che devi testare è che quel metodo di classe è stato chiamato due volte (se [come ho capito per ora], hai un "primario" e "secondario" Blog per ogni Site e che le associazioni sono configurate (Non ho ancora trovato un ottimo modo per farlo su rotaie, di solito non lo collaudo)

Inoltre, stai ignorando il metodo di creazione di ActiveRecord quando chiami Site.create? Se così fosse, ti suggerirei di fare un nuovo metodo di classe sul sito denominato qualcos'altro (Site.with_default_blogs forse?). Questo è solo l'abitudine generale di mine, ignorando roba generalmente causa problemi in seguito ai progetti.

Problemi correlati