2009-08-13 15 views
16

Ich arbeite an einigen Gurkengeschichten für eine 'Anmeldung' Anwendung, die eine Anzahl von Schritten hat.Sitzungsvariablen mit Gurkengeschichten

Anstatt dann eine Huuuuuuuge Geschichte zu schreiben, um alle Schritte auf einmal zu erfassen, die wäre, würde ich lieber durch jede Aktion im Controller wie ein normaler Benutzer arbeiten. Mein Problem hier ist, dass ich die Konto-ID speichern, die im ersten Schritt als eine Sitzungsvariable erstellt wird, also wenn Schritt 2, Schritt 3 usw. besucht werden, werden die vorhandenen Registrierungsdaten geladen.

Ich bin mir bewusst, auf controller.session[..] innerhalb RSpec Spezifikationen zugreifen zu können, aber wenn ich versuche, dies in Gurkengeschichten zu tun, schlägt es mit dem folgenden Fehler fehl (und ich habe auch gelesen, dass dies ein Anti-Muster usw. ist) ..):

Mit controller.session [: was auch immer] oder session [: was auch immer]

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

Verwendung Sitzung (: was auch immer)

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

So scheint es, Beitritt der Session-Speicher ist nicht wirklich möglich. Was ich frage mich, wenn es möglich sein könnte (und ich denke, das Beste wäre, ..):

  1. aus Mock dem Session-Speicher usw.
  2. haben ein Verfahren innerhalb der Steuerung und Stummel, dass aus (zB get_registration die eine Instanz Variable zuweist ...)

ich durch das RSpec Buch geschaut haben (gut, entrahmte) und hatte einen Blick durch webrat etc, aber ich habe festgestellt, nicht wirklich eine Antwort auf mein Problem ...

Um ein bisschen mehr zu verdeutlichen, ist der Anmeldeprozess mehr wie eine Zustandsmaschine - z.B. der Benutzer durchläuft vier Schritte, bevor die Registrierung abgeschlossen ist - daher ist "Einloggen" nicht wirklich eine Option (es bricht das Modell, wie die Website funktioniert) ...

In meiner Spezifikation für den Controller konnte ich den Aufruf der Methode auszugeben, die das Modell basierend auf der Session-Var lädt - aber ich bin mir nicht sicher, ob die 'Antipattern'-Zeile auch für Stubs und Mocks gilt?

Danke!

Antwort

19

Mocks sind schlecht in Gurken-Szenarien - sie sind fast eine Art Antipattern.

Mein Vorschlag ist, einen Schritt zu schreiben, die tatsächlich ein Benutzer anmeldet. Ich kann es tun, um diese Art und Weise

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 

Ich stelle fest, dass die Instanzvariable @user Art von schlecht ist form aber ich denke, im Falle von Anmelden/Abmelden, @user ist definitiv hilfreich.

Manchmal nenne ich es @current_user.

+1

Vielen Dank für die Eingabe ... Ich habe meine Frage mit einigen mehr Details aktualisiert, aber was Sie vorgeschlagen haben, nicht wirklich helfen, da sie Funktionalität beinhalten würde das Hinzufügen speziell für die Tests, die mir eher wie ein "Anti-Pattern" erscheinen - aber Controller-Methoden ausstoßen würden, brechen diese Regel? –

+1

zusätzliche Funktionalität? Wie meldet sich ein Benutzer auf Ihrer Site an? Sie erwähnen Ihren Registrierungsprozess, aber nicht einloggen. Warum können Sie keine Factory für einen bereits registrierten Benutzer erstellen und sie wie oben beschrieben anmelden? Gurkengeschichten sollten genau ausdrücken, wie der Benutzer mit Ihrer Website interagiert. Ein Benutzer kann keine Controller-Methoden ausgeben. Sie haben Formulare zum Ausfüllen und Links zum Klicken. Es ist kein Abnahmetest, wenn es das Benutzerverhalten nicht nachahmt. – danpickett

+0

Ich sehe beide Seiten hier.Ich habe fast genau das oben genannte für das Feature, das Tests Authentifizierung funktioniert so, wie ich will. Für andere Features scheint es ein Schmerz zu sein, diese Capybara-Schritte wiederholen zu müssen, um sich für jedes Szenario anzumelden. Gurke kann den Datenbankzustand für den Zweck eines Tests manipulieren, warum nicht die Sitzung? –

24

Ich wiederhole danpickett in sagen, Mocks sollte möglichst in Gurke vermieden werden. Wenn Ihre App jedoch keine Anmeldeseite hat oder die Leistung ein Problem darstellt, kann es erforderlich sein, die Anmeldung direkt zu simulieren.

Dies ist ein hässlicher Hack, aber es sollte die Arbeit erledigen.

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

Dies wurde nur verwendet, um die Anmeldung für eine OAuth-Site zu überspringen. Brillant. Danke Ryan. –

3

Mein Verständnis ist, dass Sie erhalten:

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

wenn Session [] zugegriffen wird, bevor Anforderung instanziiert wurde. In meinem Fall würde ich mir vorstellen, wenn Sie webrats 'visit some_existing_path vor dem Zugriff auf Sitzung [] in Ihrer Schritt-Verteidigung setzen, wird der Fehler weggehen.

jetzt leider Sitzung scheint nicht über Schritte bestehen bleiben (zumindest konnte ich den Weg nicht finden), so dass dieses Bit an Information hilft nicht Ihre Frage zu beantworten :)

So Ich nehme an, Ryan session[:user_id] = cookies[:stub_user_id]... ist der Weg zu gehen. Obwohl, imo, Test Code in der Anwendung selbst klingt nicht richtig.

5

Ich weiß nicht, wie viel dies mehr auf die ursprüngliche Frage betrifft, aber ich beschloss, trotzdem im Geist der Diskussion zu veröffentlichen ...

Wir haben eine Gurke Testsuite, die> 10 Minuten dauert laufen Also wollten wir etwas optimieren. In unserer App löst der Login-Prozess eine Menge zusätzlicher Funktionalität aus, die für die meisten Szenarien irrelevant ist. Deshalb wollten wir das überspringen, indem wir die Session-Benutzer-ID direkt setzen.

Ryanb Ansatz oben funktioniert gut, außer dass wir nicht mit diesem Ansatz abmelden konnten. Dies hat unsere Multi-User-Stories zum Scheitern gebracht.

Am Ende haben wir einen "Quick Login" Route erstellen, die nur in Testumgebung aktiviert ist:

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

Hier ist die entsprechende Aktion, die die Session-Variablen erstellt:

# 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 

Für uns Am Ende war es einfacher als mit dem Cookies-Array zu arbeiten. Als Bonus funktioniert dieser Ansatz auch mit Selenium/Watir.

Der Nachteil ist, dass wir Test-Code in unserer Anwendung enthalten. Persönlich glaube ich nicht, dass das Hinzufügen von Code, um die Anwendung testbarer zu machen, eine große Sünde ist, selbst wenn es ein wenig Unordnung hinzufügt. Das vielleicht größte Problem ist, dass zukünftige Testautoren herausfinden müssen, welche Art von Login sie verwenden sollen. Mit unbegrenzter Hardware-Performance würden wir natürlich nichts davon machen.

+0

Interessante Idee, und es gibt vielleicht eine Alternative zu Ihrem produktionsfertigen Code mit Testcode - vielleicht könnten Sie beim Start der Tests die neue Route injizieren und class_eval den Anwendungscontroller mit dem Quicklogin-Code umgehen ... Just ein Gedanke sowieso;) Danke für deinen Beitrag! –

16

Re. Ryans Lösung - Sie können Action öffnen in Sie Datei env.rb und legen Sie es dort in der Produktion Code-Basis zu vermeiden, dass (dank john @ Pivotal 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

Ich bin in Rails 4, und ich beobachte, dass dies verhindert, dass meine bestehenden 'before_filter'-Funktionen passieren, und verhindert, dass meine' helper_method's existieren. – Narfanator

2

Ich verwende eine Test-only-Anmelde Lösung wie Prikka's, aber ich mache alles in Rack, anstatt einen neuen Controller und Routen zu erstellen.

# 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: Ryan Lösung:

mit Capybara nicht funktioniert, es sei denn, kleine Anpassung vorgenommen:

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 

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

+0

Danke, dass du deine Lösung wieder hierher zurückgebracht hast. Das hat mir sehr geholfen, es funktioniert wie ein Zauber. – Pedro

+1

Für mich löst das nur einen Fehler aus: 'undefinierte Methode 'current_session' für # ' Könnten Sie genauer bestimmen, wo in Ihrer Anwendung Sie diesen Code ablegen müssen? – Ajedi32

0

Eine weitere leichte Variante:

# 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

Ich bekomme diesen Fehler! 'undefinierte Methode 'post' für # (NoMethodError)'. Sieht so aus, als könnten wir zumindest nicht mit Selen posten. –

1

Warum verwenden Sie FactoryGirl oder (Fixjour oder Fabricator) nicht mit Devise (oder Authlogic) und SentientUser? Dann können Sie einfach schnüffeln, welcher Benutzer bereits eingeloggt ist!

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

@ Ajedi32 lief ich in das gleiche Problem (nicht definierte Methode 'CURRENT_SESSION' für Capybara :: RackTest :: Treiber) und setzen diese in meinem Schritt Definition das Problem für mich festgelegt:

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 

In meiner Controller-Aktion habe ich auf Cookies [: stub_user_id] statt auf cookie_jar [: stub_user_id]

0

Nach einer Menge Seelenforschung und Surfen im Internet, entschied ich mich schließlich für eine sehr einfache und offensichtliche Lösung.

Die Verwendung von Cookies fügt zwei Probleme hinzu. Zuerst gibt es in der Anwendung Code zum Testen und zweitens gibt es das Problem, dass das Erstellen von Cookies in Cucumber schwierig ist, wenn man etwas anderes als einen Rack-Test verwendet. Es gibt verschiedene Lösungen für das Cookie-Problem, aber alle von ihnen sind ein bisschen schwierig, einige stellen Mocks vor, und alle von ihnen sind, was ich 'tricky' nenne. Eine solche Lösung ist here.

Meine Lösung ist die folgende. Dies verwendet die HTTP-Standardauthentifizierung, könnte jedoch für die meisten Zwecke verallgemeinert werden.

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 tut, was auch immer die normale beglaubigen tut, außer es keine zeitraubende Teile umgeht. In meinem Fall verwendet die echte Authentifizierung LDAP, das ich vermeiden wollte.

Ja ... es ist ein bisschen eklig, aber es ist klar, einfach und offensichtlich. Und ... keine andere Lösung, die ich gesehen habe, ist sauberer oder klarer.

Hinweis: Wenn der Benutzername nicht 'testuser' lautet, wird der normale Pfad verwendet, damit er getestet werden kann.

Hope this anderen hilft ...

Verwandte Themen