2009-08-13 9 views
16

Pracuję nad niektórymi historiami Ogórek dla aplikacji "zarejestruj się", która ma wiele kroków.Zmienne sesji z ogórkowymi historiami

Zamiast pisać opowiadanie Huuuuuuuge, aby objąć wszystkie kroki naraz, co byłoby złe, wolę pracować przez każdą akcję w kontrolerze jak zwykły użytkownik. Mój problem polega na tym, że przechowuję identyfikator konta, który jest tworzony w pierwszym kroku jako zmienna sesji, więc gdy krok 2, krok 3 itd. Są odwiedzane, ładowane są istniejące dane rejestracyjne.

Jestem świadomy, że mogę uzyskać dostęp do controller.session[..] w specyfikacji RSpec, jednak gdy próbuję to zrobić w opowieści Cucumber nie powiedzie się z następującego błędu (i, I've również przeczytać gdzie to jest anty-wzór itp. ..)

Stosując controller.session [: niezależnie] lub [sesji: cokolwiek]

You have a nil object when you didn't expect it! 
The error occurred while evaluating nil.session (NoMethodError) 

Stosując sesji: (co)

wrong number of arguments (1 for 0) (ArgumentError) 

Wygląda na to, że przystąpienie do sesji nie jest możliwe. Co Zastanawiam się, czy to może być możliwe (i myślę, co byłoby najlepszym ..):

  1. Mock się sklep sesja itp
  2. mieć metodę w kontrolerze i skrótową że out (np get_registration która przypisuje zmiennej instancji ...)

ja przejrzałem książki RSpec (dobrze, odtłuszczone) i miał wygląd poprzez WebRat etc, ale ja naprawdę nie znalazłem odpowiedzi na mój problem ...

Aby wyjaśnić nieco więcej, proces rejestracji jest bardziej podobnie jak automat stanów - np. użytkownik postępuje przez cztery kroki, zanim rejestracja zostanie zakończona - stąd "logowanie" nie jest tak naprawdę opcją (łamie model działania witryny) ...

W mojej specyfikacji dla kontrolera byłem w stanie wyodrębnić wywołanie metody, która ładuje model w oparciu o sesję var - ale nie jestem pewien, czy linia 'antipattern' odnosi się również do kodów pośredniczących i makiet?

Dzięki!

Odpowiedz

19

mocks są złe w scenariuszach ogórka - są prawie rodzajem antipattern.

Moja sugestia jest napisanie krok, który faktycznie loguje użytkownika w. I zrobić to w ten sposób

Given I am logged in as "[email protected]" 

Given /^I am logged in as "(.*)"$/ do |email| 
    @user = Factory(:user, :email => email) 
    @user.activate! 
    visit("/session/new") 
    fill_in("email", :with => @user.email) 
    fill_in("password", :with => @user.password) 
    click_button("Sign In") 
end 

Zdaję sobie sprawę, że instancja zmiennej @user jest trochę zła forma, ale myślę, że w przypadku logowanie/wylogowanie, posiadanie @user jest zdecydowanie pomocne.

Czasami nazywam to @current_user.

+1

Dzięki za wejście ... Ja aktualizowane na moje pytanie z trochę więcej szczegółów, ale co masz sugerowane naprawdę nie pomaga gdyż obejmować dodanie funkcjonalności specjalnie dla testy, które dla mnie wydają się bardziej "anty-wzorcem" - ale czy podkręcenie metod kontrolerów złamałoby tę zasadę? –

+1

dodatkowa funkcjonalność? W jaki sposób użytkownik loguje się do Twojej witryny? Wspominasz proces rejestracji, ale nie logujesz się. Dlaczego nie możesz utworzyć fabryki dla już zarejestrowanego użytkownika i zalogować się tak, jak pokazałem ci powyżej? Historie dotyczące ogórków powinny dokładnie odzwierciedlać sposób interakcji użytkownika z witryną. Użytkownik nie może zablokować metod kontrolera. Mają formularze do wypełnienia i linki do kliknięcia. Nie jest to test akceptacji, jeśli nie naśladuje zachowania użytkownika. – danpickett

+0

Widzę tu obie strony.Mam prawie dokładnie powyższe dla funkcji, która testuje uwierzytelnianie działa tak, jak chcę. W przypadku innych funkcji wydaje się, że trzeba powtarzać te kroki, aby zalogować się w każdym scenariuszu. Ogórek może manipulować stanem bazy danych dla celów testu, dlaczego nie sesja? –

24

Powtórzę danpickett mówiąc, że należy unikać mocks, kiedy tylko jest to możliwe w Cucumber. Jeśli jednak twoja aplikacja nie ma strony logowania, lub może wydajność jest problemem, może być konieczna symulacja logowania bezpośrednio.

To jest brzydki hack, ale powinien wykonać zadanie.

Given /^I am logged in as "(.*)"$/ do |email| 
    @current_user = Factory(:user, :email => email) 
    cookies[:stub_user_id] = @current_user.id 
end 

# in application controller 
class ApplicationController < ActionController::Base 
    if Rails.env.test? 
    prepend_before_filter :stub_current_user 
    def stub_current_user 
     session[:user_id] = cookies[:stub_user_id] if cookies[:stub_user_id] 
    end 
    end 
end 
+0

Po prostu używa się tego, aby pominąć logowanie do witryny OAuth. Znakomity. Dzięki, Ryan. –

3

Rozumiem, że masz:

You have a nil object when you didn't expect it! 
The error occurred while evaluating nil.session (NoMethodError) 

podczas sesji [] jest dostępne przed kupna została instancja. W twoim przypadku wyobrażam sobie, że jeśli umieścisz webrats 'visit some_existing_path przed uzyskaniem dostępu do sesji [] w twojej defenicji, błąd zniknie.

Teraz, niestety, sesja nie wydaje się utrzymywać w poprzek etapach (przynajmniej ja nie mogłem znaleźć sposób), więc ten bit informacji nie pomaga odpowiedzieć na pytanie :)

Tak , Przypuszczam, że droga Ryana to session[:user_id] = cookies[:stub_user_id].... Chociaż imo, kod związany z testami w samej aplikacji nie brzmi dobrze.

5

nie wiem ile to odnosi się do pierwotnego pytania już, ale mimo to postanowił odpowiedzieć w duchu dyskusji ...

Mamy zestaw testów, które odbywają ogórka> 10 minut, aby uruchomić więc chcieliśmy dokonać optymalizacji. W naszej aplikacji proces logowania wyzwala DUŻO dodatkowej funkcjonalności, która jest nieistotna dla większości scenariuszy, więc chcieliśmy to pominąć poprzez bezpośrednie ustawienie identyfikatora użytkownika sesji.

Powyższe podejście Ryanb działało dobrze, z tym, że nie mogliśmy się wylogować przy użyciu tego podejścia. Sprawiło to, że nasze historie dotyczące wielu użytkowników zawiodły.

Skończyło się tworząc „szybkiego logowania” trasę, która jest dostępna tylko w środowisku testowym:

# in routes.rb 
map.connect '/quick_login/:login', :controller => 'logins', :action => 'quick_login' 

Oto odpowiednia akcja, która tworzy zmienną sesji:

# in logins_controller.rb 
class LoginsController < ApplicationController 
    # This is a utility method for selenium/webrat tests to speed up & simplify the process of logging in. 
    # Please never make this method usable in production/staging environments. 
    def quick_login 
    raise "quick login only works in cucumber environment! it's meant for acceptance tests only" unless Rails.env.test? 
    u = User.find_by_login(params[:login]) 
    if u 
     session[:user_id] = u.id 
     render :text => "assumed identity of #{u.login}" 
    else 
     raise "failed to assume identity" 
    end 
    end 
end 

Dla nas to skończyło się być prostsze niż praca z tablicą cookie. Jako bonus, podejście to działa również z Selenium/Watir.

Minusem jest to, że dołączamy kod testowy do naszej aplikacji. Osobiście nie uważam, że dodanie kodu, aby aplikacja była bardziej testowalna, jest ogromnym grzechem, nawet jeśli dodaje trochę bałaganu. Być może największym problemem jest to, że przyszli autorzy testów muszą dowiedzieć się, jakiego rodzaju logowania powinni używać. Przy nieograniczonej wydajności sprzętu oczywiście nic byśmy nie robili.

+0

Interesujący pomysł i może istnieć alternatywa dla pieprzonego kodu przygotowanego przez producenta za pomocą kodu testowego - być może podczas uruchamiania testów można wprowadzić nową trasę i class_eval kontroler aplikacji do obsługi kodu szybkiego logowania ... Just myśl i tak;) Dzięki za twój wkład! –

16

Re. Rozwiązanie Ryana - można otworzyć ActionController w ty env.rb plik i umieścić go tam, aby uniknąć wprowadzenia w swojej bazie kodu produkcyjnego (dzięki Janowi @ obrotowe Labs)

# in features/support/env.rb 
class ApplicationController < ActionController::Base 
    prepend_before_filter :stub_current_user 
    def stub_current_user 
    session[:user_id] = cookies[:stub_user_id] if cookies[:stub_user_id] 
    end 
end 
+0

Jestem w Rails 4, i obserwuję, że to "zapobiega" istniejącym funkcjom 'before_filter', a także zapobiega istnieniu moich' helper_method's. – Narfanator

2

używam tylko do testowania logowania rozwiązanie takie jak Prikka's, ale robię to wszystko w Rack zamiast tworzenia nowego kontrolera i tras.

# in config/environments/cucumber.rb: 

config.middleware.use (Class.new do 
    def initialize(app); @app = app; end 
    def call(env) 
    request = ::Rack::Request.new(env) 
    if request.params.has_key?('signed_in_user_id') 
     request.session[:current_user_id] = request.params['signed_in_user_id'] 
    end 
    @app.call env 
    end 
end) 

# in features/step_definitions/authentication_steps.rb: 
Given /^I am signed in as ([^\"]+)$/ do |name| 
    user = User.find_by_username(name) || Factory(:user, :username => name) 
    sign_in_as user 
end 

# in features/step_definitions/authentication_steps.rb: 
Given /^I am not signed in$/ do 
    sign_in_as nil 
end 

module AuthenticationHelpers 
    def sign_in_as(user) 
    return if @current_user == user 
    @current_user = user 
    get '/', { 'signed_in_user_id' => (user ? user.to_param : '') } 
    end 
end 

World(AuthenticationHelpers) 
4

Re: Ryana rozwiązanie:

Nie działa z Kapibara, chyba że niewielka adaptacja zrobić:

rack_test_driver = Capybara.current_session.driver 
cookie_jar = rack_test_driver.current_session.instance_variable_get(:@rack_mock_session).cookie_jar 
@current_user = Factory(:user) 
cookie_jar[:stub_user_id] = @current_user.id 

(tutaj: https://gist.github.com/484787)

+0

Dziękuję, że przyczyniłeś się do rozwiązania tutaj, To pomogło mi bardzo, działa jak czar. – Pedro

+1

Dla mnie to po prostu wywołanie błędu: 'niezdefiniowana metoda 'current_session' dla # ' Czy mógłbyś dokładniej określić, gdzie w twojej aplikacji potrzebujesz umieścić ten kod? – Ajedi32

0

Innym niewielkie zmiany:

# In features/step_definitions/authentication_steps.rb: 

class SessionsController < ApplicationController 
    def create_with_security_bypass 
    if params.has_key? :user_id 
     session[:user_id] = params[:user_id] 
     redirect_to :root 
    else 
     create_without_security_bypass 
    end 
    end 

    alias_method_chain :create, :security_bypass 
end 

Given %r/^I am logged in as "([^"]*)"$/ do |username| 
    user = User.find_by_username(username) || Factory(:user, :username => username) 
    page.driver.post "/session?user_id=#{user.id}" 
end 
+1

Otrzymuję ten błąd! 'niezdefiniowana metoda 'post' dla # (NoMethodError)'. Wygląda na to, że nie możemy publikować co najmniej z Selenium. –

1

Dlaczego nie używasz FactoryGirl lub (Fixjour lub Fabricator) z Devise (lub Authlogic) i SentientUser? Następnie możesz po prostu wyczuć, który użytkownik jest już zalogowany!

@user = Factory(:user)  # FactoryGirl 
sign_in @user    # Devise 
User.current.should == @user # SentientUser 
2

@ Ajedi32 wpadłem na tym samym numerze (metoda niezdefiniowany 'current_session' dla Kapibara :: RackTest :: Driver) i umieszczenie tego w mojej definicji kroku Naprawiono problem dla mnie:

rack_test_browser = Capybara.current_session.driver.browser 

cookie_jar = rack_test_browser.current_session.instance_variable_get(:@rack_mock_session).cookie_jar 
cookie_jar[:stub_user_id] = @current_user.id 

W mojej kontrolerce odwołałem się do plików cookie [: stub_user_id], zamiast cookie_jar [: stub_user_id]

0

Po wielu poszukiwaniach dusz i surfowaniu po Internecie, ostatecznie zdecydowałem się na bardzo proste i oczywiste rozwiązanie.

Korzystanie z plików cookie powoduje dwa problemy. Najpierw masz kod w aplikacji specyficznej dla testowania, a po drugie jest problem, że tworzenie ciasteczek w ogórku jest trudne, gdy używasz czegoś innego niż test rackowy. Istnieją różne rozwiązania problemu z ciasteczkami, ale wszystkie z nich są trudnym zadaniem, niektóre wprowadzają makiety, a wszystkie są tak zwane "trudne". Jednym z takich rozwiązań jest here.

Moje rozwiązanie jest następujące. Wykorzystuje to podstawowe uwierzytelnianie HTTP, ale można je uogólnić dla większości elementów.

authenticate_or_request_with_http_basic "My Authentication" do |user_name, password| 
    if Rails.env.test? && user_name == 'testuser' 
     test_authenticate(user_name, password) 
    else 
     normal_authentication 
    end 
    end 

test_authenticate robi to, co robi normalne uwierzytelnianie, oprócz tego, że omija wszelkie czasochłonne części. W moim przypadku prawdziwe uwierzytelnianie używa LDAP, którego chciałem uniknąć.

Tak ... to jest trochę brutto, ale jest jasne, proste i oczywiste. I ... żadne inne rozwiązanie, które widziałem, nie jest czystsze ani jaśniejsze.

Uwaga, jedną z cech jest to, że jeśli nazwa_użytkownika nie jest "testserem", to normalna ścieżka jest pobierana, aby można było przetestować.

Nadzieja to pomaga innym ...