2012-02-13 19 views
6

Obecnie staram się nieco kontrolować specyfikacje kontrolera w sposób SUCHO i zwięzły, a na podstawie jednego potwierdzenia. Występują pewne trudności, szczególnie jeśli chodzi o miejsce faktycznego wywołania żądania kontrolera w strukturze zagnieżdżonej, aby dopasować różne przypadki brzegowe.Specyfikacja kontrolera DRY z RSpecem

Oto przykład, uproszczone do wykazania problem:

describe MyController do 
    let(:item) { Factory(:item) } 
    subject { response } 

    describe "GET #show" do 
    before(:each) do 
     get :show 
    end 

    context "published item" do 
     it { should redirect_to(success_url) } 
    end 

    context "unpublished item" do 
     before(:each) do 
     item.update_attribute(published: false) 
     end 

     it { should redirect_to(error_url) } 
    end 
    end 
end 

Oczywiste jest to wymyślony przykład, ale pokazuje, co chciałbym zrobić, a co nie działa. Głównie problemem jest blok before w kontekście "niepublikowanym". Co się dzieje, to że zmiana, którą wprowadziłem w danych konfiguracyjnych, rzeczywiście się zdarza po wywołaniu wywołania get ze względu na sposób zagnieżdżenia kontekstów, więc przykład w tym kontekście faktycznie działa ze scenariuszem początkowym, a nie tym, który zamierzam.

Rozumiem, dlaczego tak się dzieje i jak zagnieżdżają się konteksty. Chyba co ja jak mieć jakiś sposób powiedzieć rspec co chciałbym go uruchomić prawy po dowolny before haki jeszcze prawo przed żadnych twierdzeń w danym kontekście. Byłoby to idealne dla specyfikacji kontrolera. Chciałbym skorzystać z zagnieżdżania w specyfikacji kontrolera, aby stopniowo zwiększać wariacje przypadków krawędziowych bez konieczności rozpraszania połączenia get, a nawet wywoływania pomocnika do_get w każdym z moich asercji it. Szczególnie denerwuje to zachowanie synchronizacji z dowolnymi niestandardowymi makrami, których używam.

Czy jest coś w RSpec, aby to osiągnąć? Czy są jakieś sztuczki, których mogę użyć, aby się zbliżyć? Wydawałoby się idealnie pasować do sposobu, w jaki widziałem wiele osób piszących specyfikacje kontrolerów; z tego, co odkryłem, ludzie w zasadzie ustalili, że przed każdym asercją są wywoływani pomocnicy do_get. Czy istnieje lepszy sposób?

Odpowiedz

6

suchej stanach zasadę, że „Każdy kawałek wiedzy musi mieć jeden, jednoznaczną, miarodajnej reprezentacji w systemie.” To, co robisz, to coś więcej na temat uratowania kilku postaci tu i tam, niż utrzymywania rzeczy w stanie SUCHYM, a wynikiem jest splątana sieć zależności w górę iw dół hierarchii, która, jak widać, jest suką, aby zrobić to, co trzeba. chcesz to, a co za tym idzie kruche i kruche.

Zacznijmy od tego, co masz napisane w sposób, który jest gadatliwy i działa:

describe MyController do 
    describe "GET #show" do 
    context "published item" do 
     it "redirects to the success url" do 
     item = Factory(:item, published: true) 
     get :show, :id => item.id 
     response.should redirect_to success_url 
     end 
    end 

    context "unpublished item" do 
     it "redirects to the error url" do 
     item = Factory(:item, published: false) 
     get :show, :id => item.id 
     response.should redirect_to error_url 
     end 
    end 
    end 
end 

Teraz tylko „kawałki wiedzy”, które są kopiowane są imiona tych przykładów, które mogłyby być generowane przez matchers na końcu każdego przykładu.Problem ten można rozwiązać w czytelny sposób za pomocą metody example, który jest aliasem it:

describe MyController do 
    describe "GET #show" do 
    context "published item" do 
     example do 
     item = Factory(:item, published: true) 
     get :show, :id => item.id 
     response.should redirect_to success_url 
     end 
    end 

    context "unpublished item" do 
     example do 
     item = Factory(:item, published: false) 
     get :show, :id => item.id 
     response.should redirect_to error_url 
     end 
    end 
    end 
end 

Nie. SUCHY. I całkiem czytelny i łatwy do zmiany. Teraz, gdy zdarzy ci się dodać więcej przykładów na jednej z kontekstu, można dodać let:

describe MyController do 
    describe "GET #show" do 
    context "published item" do 
     let(:item) { Factory(:item, published: true) } 
     example do 
     get :show, :id => item.id 
     response.should redirect_to success_url 
     end 

     example do 
     # other example 
     end 
    end 
    # ... 
    end 
end 

Teraz tylko kod duplikatów (nie taka sama jak zasada DRY) jest get. Jeśli naprawdę masz na to ochotę, możesz przekazać te połączenia do metody podobnej do tej, na przykład get_show(id) lub innej, ale tak naprawdę nie kupujesz zbyt wiele w tym momencie. To nie tak, że interfejs API dla get zmieni się od ciebie, a jedynym argumentem dla get jest identyfikator item, na którym ci naprawdę zależy w tym przykładzie (więc nie ma niepotrzebnych informacji).

Jeśli chodzi o używanie subject do przechwytywania odpowiedzi i wyciągania pojedynczych linków z umowy, to po prostu sprawia, że ​​rzeczy naprawdę trudne do odczytania i nie oszczędzają dużo. W rzeczywistości, przyszło mi rozważyć użycie subject w ten sposób to be a smell.

Mam nadzieję, że to wszystko pomaga.

Pozdrawiam, David

+0

Dobra uwaga. Dodatkowa gadatliwość może być warta zachowania jasności specyfikacji, nawet jeśli wywołanie "get" nadal się powtarza. Mimo to wydaje się, że specyfikacje kontrolera, które mają specyficzny przypadek użycia odpowiedzi na działanie REST, mogłyby w jakiś sposób ograniczyć takie powtórzenia. –

+0

Chris - Zgadzam się, że jakiś skrót byłby przyjemny i jestem otwarty na ten pomysł, ale widzę, że ten, który utrzymuje to, co uważam za odpowiedni poziom jasności. Jeśli masz jakieś pomysły, proszę przesłać zapytanie o funkcję na https://github.com/rspec/rspec-rails/issues. –

3

Will

context "unpublished item" do 
    let(:item) do 
    Factory(:item, published: false) 
    end 

    it { should redirect_to(error_url) } 
end 

praca dla Ciebie? BTW, before domyślnie jest before(:each), dzięki czemu możesz OSUSZCZAĆ o trochę więcej szczegółów.

UPDATE: można również izolować przykłady z anonimowych kontekstach, takich jak:

describe "GET #show" do 
    let(:show!) do 
    get :show 
    end 

    context do 
    before { show! } 

    context "published item" do 
     it { should redirect_to(success_url) } 
    end 

    # another examples with show-before-each 
    end 

    context "unpublished item" do 
    before do 
     item.update_attribute(published: false) 
     show! 
    end 

    it { should redirect_to(error_url) } 
    end 
end 
+0

To nie wszystko. Wolałbym raczej edytować istniejący element zamiast go całkowicie zastąpić, co zrobi to rozwiązanie. Rozumowanie polega na tym, że łatwiej jest budować konteksty wielokrotnego użytku, które wprowadzają przyrostowe zmiany w stanie konfiguracji, aby umożliwić przetestowanie różnych permutacji poprzez połączenie tych kontekstów. –

+0

Zobacz inne podejście w zaktualizowanej odpowiedzi. Myślę, że nie ma innego sposobu na zmianę logiki zagnieżdżonych haków. –

+0

Skończyło się na użyciu podobnego rozwiązania. Wciąż jest trochę brudno, ale działa. Musisz tylko upewnić się, że 'show!' Jest poprawnie wywoływane w kontekstach zagnieżdżonych i upewnij się, że nie zostanie wywołane dwa razy. Jest bardziej mentalny na głowie, niż chcę, ale na razie to robi. –

Powiązane problemy