2010-09-13 11 views
8

Podejmuję wspólny wysiłek, aby owinąć głowę wokół Rspec, aby przejść do bardziej rozwiniętego wzoru rozwoju TDD/BDD. Jestem jednak daleko i walczę z niektórymi podstawami:Kiedy i kiedy nie ma być kikutem/kpiną z testu

Kiedy dokładnie powinienem używać makiet/kikutów, a kiedy nie powinienem?

Weźmy na przykład taki scenariusz: Mam Site model, który has_many :blogs i Blog modelu has_many :articles. W moim modelu Site mam filtr zwrotny, który tworzy domyślny zestaw blogów i artykułów dla każdej nowej witryny. Chcę przetestować ten kod, tak tu idzie:

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 

Teraz, jeśli mogę uruchomić ten test, wszystko przemija. Jest jednak również dość powolny, ponieważ tworzy nową witrynę, dwa nowe blogi i trzy nowe artykuły - na każdy pojedynczy test! Zastanawiam się, czy jest to dobry kandydat do używania kodów pośredniczących? Spróbujmy:

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 

Teraz wszystkie testy wciąż mijają, a sprawy są nieco szybsze. Ale podwoiłem długość moich testów i całe ćwiczenie wydaje mi się kompletnie bezsensowne, ponieważ nie testuję już swojego kodu, testuję tylko moje testy.

Teraz albo ja całkowicie brakowało punktu drwi/odcinki, albo ja zbliża to fundamentalnie złe, ale mam nadzieję, ktoś może być w stanie albo:

  • Pomóż mi testuje powyżej więc używa skrótów lub makiet w sposób, który faktycznie testuje mój kod, a nie moje testy.
  • Albo, powiedz mi, czy powinienem nawet używać tutaj kodów pośrednich - lub czy w rzeczywistości jest to całkowicie niepotrzebne i powinienem pisać te modele do testowej bazy danych.

Odpowiedz

2

Ale ja podwojona długość moich testów i całe ćwiczenie po prostu wydaje mi się zupełnie bez sensu, bo ja już nie testuje mój kod, jestem po prostu testowania moich testów.

To jest klucz tutaj. Testy, które nie testują Twojego kodu, nie są przydatne. Jeśli możesz negatywnie zmienić kod, który testy mają testować, a testy nie zawiedzie, nie warto ich mieć.

Zgodnie z zasadą, nie lubię udawać/odgrażać niczego, chyba że muszę. Na przykład, kiedy piszę test kontrolera i chcę się upewnić, że odpowiednia akcja ma miejsce, gdy rekord nie może się zapisać, łatwiej jest znaleźć metodę obiektu, aby zwrócić false, zamiast ostrożnie tworzyć parametry tylko więc aby upewnić się, że model nie zapisuje.

Innym przykładem jest helper o nazwie admin?, który po prostu zwraca wartość true lub false w zależności od tego, czy aktualnie zalogowany użytkownik jest administratorem, czy nie. Nie chciałem, aby przejść przez udaje login użytkownika, więc zrobiłem to:

# 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 

Jako kompromis, warto zajrzeć do Shoulda. W ten sposób możesz po prostu upewnić się, że twoje modele mają skojarzenie zdefiniowane jako i ufają, że Rails jest wystarczająco przetestowany, aby skojarzenie "działało" bez konieczności tworzenia skojarzonego modelu, a następnie jego liczenia.

Mam model o nazwie Member, w którym zasadniczo wszystko z mojej aplikacji jest powiązane. Ma zdefiniowane 10 skojarzeń. mogłem przetestować każdy z tych związków, czy może po prostu to zrobić:

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

Dziękuję, to jest pomocna odpowiedź :) :) – aaronrussell

1

Właśnie dlatego używam odcinki/kpi bardzo rzadko (naprawdę tylko wtedy, gdy jadę do uderzania zewnętrznego usługa). Oszczędność czasu nie jest warta dodatkowej złożoności.

Są lepsze sposoby na przyspieszenie czasu testowania, a Nick Gauthier daje dobrą rozmowę obejmującą kilka z nich - patrz video i slides.

Ponadto, myślę, że dobrym rozwiązaniem jest wypróbowanie bazy danych sqlite w pamięci dla twoich przebiegów testowych. To powinno znacznie zmniejszyć czas bazy danych, ponieważ nie trzeba uderzać w dysk dla wszystkiego. Nie próbowałem tego jednak sam (przede wszystkim używam MongoDB, który ma tę samą zaletę), więc stąpaj ostrożnie. Here's dość niedawny wpis na blogu na ten temat.

+0

Dzięki za twoją odpowiedź i linki. Oglądanie wideo już teraz. – aaronrussell

1

Nie jestem pewna, czy zgadzam się z innymi. Prawdziwym problemem (jak ja to widzę) jest to, że testujesz wiele ciekawych zachowań za pomocą tych samych testów (zachowanie i tworzenie). Z powodów, dla których jest to złe, zobacz tę rozmowę: http://www.infoq.com/presentations/integration-tests-scam. Zakładam na resztę tej odpowiedzi, że chcesz przetestować, że tworzenie jest tym, co chcesz przetestować.

Testy izolacjonistyczne często wydają się nieporęczne, ale często wynikają z tego, że mają lekcje projektowania, aby Cię uczyć. Poniżej przedstawiam kilka podstawowych rzeczy, które mogę zobaczyć (nie widząc kodu produkcyjnego, nie mogę zrobić zbyt wiele dobrego).

Po pierwsze, aby zapytać o projekt, czy dodanie do bloga artykułów ma sens? A co z metodą klasy na Blog o nazwie coś podobnego do Blog.with_one_article. Oznacza to, że wszystko, co musisz przetestować, to to, że ta metoda klasy została wywołana dwa razy (jeśli [jak na razie rozumiem], masz "podstawowy" i "drugorzędny" Blog dla każdego Site i że skojarzenia są skonfigurowane (Nie znalazłem jeszcze świetnego sposobu na zrobienie tego w szynach, zazwyczaj go nie testuję)

Co więcej, czy nadpisujesz metodę tworzenia ActiveRecord, gdy dzwonisz pod numer Site.create? Jeśli tak, proponuję zrobienie nowa metoda klasy na stronie o nazwie coś innego (Site.with_default_blogs ewentualnie?) To jest tylko ogólny nawyk, nadrzędne rzeczy generalnie powodują problemy później w projektach

Powiązane problemy