2009-08-13 27 views
16

Tôi đang làm việc trên một số câu chuyện về dưa chuột cho ứng dụng 'đăng ký' có một số bước.Biến phiên với Câu chuyện về dưa chuột

Thay vì viết một câu chuyện của Hữuuuuuuge để bao gồm tất cả các bước cùng một lúc, sẽ là xấu, tôi muốn làm việc thông qua từng hành động trong bộ điều khiển như người dùng thông thường. Vấn đề của tôi ở đây là tôi đang lưu trữ ID tài khoản được tạo trong bước đầu tiên dưới dạng biến phiên, vì vậy khi bước 2, bước 3, vv được truy cập, dữ liệu đăng ký hiện có được tải.

Tôi biết có thể truy cập controller.session[..] trong thông số kỹ thuật RSpec tuy nhiên khi tôi cố gắng làm điều này trong câu chuyện dưa chuột không thành công với lỗi sau (và, tôi cũng đọc ở đâu đó đây là mẫu chống vân vân, v.v. ..):

Sử dụng controller.session [: bất cứ điều gì] hoặc phiên [: bất cứ điều gì]

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

Sử dụng phiên (: bất cứ điều gì)

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

Vì vậy, có vẻ như việc gia nhập cửa hàng phiên không thực sự khả thi. Những gì tôi đang tự hỏi là nếu nó có thể là có thể (và tôi đoán đó sẽ là tốt nhất ..):

  1. Mock ra các cửa hàng phiên vv
  2. Có một phương pháp trong điều khiển và còn sơ khai mà ra (ví dụ: get_registration chỉ định một biến mẫu ...)

Tôi đã xem qua cuốn sách RSpec (tốt, lướt qua) và xem qua WebRat v.v ... nhưng tôi chưa thực sự tìm thấy câu trả lời cho vấn đề của mình ...

Để làm rõ hơn một chút, quá trình đăng ký là nhiều hơn giống như một máy nhà nước - ví dụ người dùng tiến hành thông qua bốn bước trước khi đăng ký hoàn tất - do đó 'đăng nhập' không thực sự là một tùy chọn (nó phá vỡ mô hình trang web hoạt động như thế nào) ...

Trong thông số kỹ thuật của mình cho bộ điều khiển, tôi có thể để stub ra các cuộc gọi đến phương pháp tải mô hình dựa trên phiên var - nhưng tôi không chắc chắn nếu dòng 'antipattern' cũng áp dụng cho stubs cũng như mocks?

Cảm ơn!

Trả lời

19

mocks là xấu trong các tình huống dưa chuột - chúng gần như là loại của một antipattern.

Đề nghị của tôi là viết một bước mà thực sự đăng nhập người dùng. Tôi làm theo cách này

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 

Tôi nhận ra rằng các trường hợp biến @user là loại xấu hình thức-nhưng tôi nghĩ rằng trong trường hợp đăng nhập/đăng xuất, có @user chắc chắn hữu ích.

Đôi khi tôi gọi nó là @current_user.

+1

Cảm ơn bạn đã nhập ... Tôi đã cập nhật câu hỏi của mình với một số chi tiết khác, nhưng những gì bạn đã đề xuất không thực sự hữu ích vì nó sẽ liên quan đến việc thêm chức năng đặc biệt cho các bài kiểm tra, mà đối với tôi có vẻ giống như một 'mô hình chống' - nhưng, sẽ loại bỏ các phương pháp điều khiển phá vỡ quy tắc này? –

+1

chức năng bổ sung? Người dùng đăng nhập vào trang web của bạn như thế nào? Bạn đề cập đến quá trình đăng ký của bạn nhưng không đăng nhập. Tại sao bạn không thể tạo một nhà máy cho một người dùng đã đăng ký và đăng nhập chúng như tôi đã chỉ cho bạn ở trên? Câu chuyện về dưa chuột phải thể hiện chính xác cách người dùng tương tác với trang web của bạn. Người dùng không thể khai thác các phương thức điều khiển. Họ có các biểu mẫu để điền và liên kết để nhấp. Nó không phải là một thử nghiệm chấp nhận nếu nó không bắt chước hành vi của người dùng. – danpickett

+0

Tôi thấy cả hai mặt ở đây.Tôi đã có gần như chính xác ở trên cho các tính năng kiểm tra xác thực hoạt động theo cách tôi muốn. Đối với các tính năng khác, có vẻ như một nỗi đau phải lặp lại các bước capybara đó để đăng nhập cho mọi tình huống. Dưa chuột có thể điều khiển trạng thái cơ sở dữ liệu cho mục đích thử nghiệm, tại sao không phải là phiên? –

24

Tôi sẽ lặp lại danpickett trong khi nói mocks nên tránh bất cứ khi nào có thể trong Dưa chuột. Tuy nhiên, nếu ứng dụng của bạn không có trang đăng nhập hoặc có lẽ hiệu suất là một vấn đề, thì có thể cần phải mô phỏng trực tiếp đăng nhập.

Đây là một hack xấu xí, nhưng nó sẽ hoàn thành công việc.

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

Chỉ cần sử dụng tính năng này để bỏ qua thông tin đăng nhập cho trang web OAuth. Rực rỡ. Cảm ơn Ryan. –

3

hiểu biết của tôi là bạn có được:

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

khi phiên [] được truy cập trước yêu cầu đã được khởi tạo. Trong trường hợp của bạn, tôi sẽ tưởng tượng nếu bạn đặt webrats 'visit some_existing_path trước khi truy cập phiên [] trong bước của bạn defenition, lỗi sẽ biến mất.

Bây giờ, thật không may, phiên dường như không tồn tại qua các bước (ít nhất, tôi không thể tìm đường đi), vì vậy chút thông tin này không giúp để trả lời câu hỏi của bạn :)

Vì vậy, , Tôi cho rằng, session[:user_id] = cookies[:stub_user_id]... của Ryan là con đường để đi. Mặc dù, imo, kiểm tra mã liên quan trong bản thân ứng dụng không đúng.

5

tôi không biết bao nhiêu điều này liên quan đến câu hỏi ban đầu nữa, nhưng tôi quyết định gửi anyway trong tinh thần của cuộc thảo luận ...

Chúng tôi có một bộ kiểm tra dưa chuột mà mất> 10 phút để chạy vì vậy chúng tôi muốn thực hiện một số tối ưu hóa. Trong ứng dụng của chúng tôi, quá trình đăng nhập kích hoạt rất nhiều chức năng bổ sung không liên quan đến đa số các kịch bản, vì vậy chúng tôi muốn bỏ qua điều đó bằng cách đặt id người dùng phiên trực tiếp.

Cách tiếp cận của Ryanb ở trên hoạt động tốt, ngoại trừ việc chúng tôi không thể đăng xuất bằng cách tiếp cận đó. Điều này làm cho câu chuyện đa người dùng của chúng tôi thất bại.

Chúng tôi đã kết thúc việc tạo ra một "nhanh chóng đăng nhập" con đường mà chỉ được kích hoạt trong môi trường thử nghiệm:

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

Đây là hành động tương ứng tạo ra các biến session:

# 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 

Đối với chúng tôi đây cuối cùng trở nên đơn giản hơn so với làm việc với mảng cookie. Như một phần thưởng, cách tiếp cận này cũng làm việc với Selenium/Watir.

Nhược điểm là chúng tôi đang bao gồm mã liên quan đến thử nghiệm trong ứng dụng của mình. Cá nhân tôi không nghĩ rằng việc thêm mã để làm cho ứng dụng dễ kiểm thử hơn là một tội lỗi lớn, ngay cả khi nó thêm một chút lộn xộn. Có lẽ vấn đề lớn nhất là các tác giả thử nghiệm trong tương lai cần phải tìm ra loại đăng nhập nào họ nên sử dụng. Với hiệu suất phần cứng không giới hạn, chúng tôi rõ ràng sẽ không thực hiện bất kỳ thao tác nào trong số này.

+0

Ý tưởng thú vị và có thể có một giải pháp thay thế cho việc mã hóa mã sản phẩm của bạn với mã thử nghiệm - có lẽ khi thử nghiệm của bạn bắt đầu, bạn có thể tiêm tuyến mới và class_eval trình điều khiển ứng dụng để xử lý mã quicklogin ... Chỉ cần một suy nghĩ anyways;) Cảm ơn sự đóng góp của bạn! –

16

Re. giải pháp Ryan - bạn có thể mở ActionController trong bạn env.rb tập tin và đặt nó ở đó để tránh đặt trong mã cơ sở sản xuất của mình (nhờ vào các phòng thí nghiệm john @ then chốt)

# 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

Tôi đang ở trong Rails 4, và tôi đang quan sát rằng điều này "ngăn chặn" các chức năng 'before_filter' hiện tại của tôi đang xảy ra, cũng như ngăn chặn' helper_method' của tôi khỏi hiện tại. – Narfanator

2

tôi sử dụng một thử nghiệm chỉ đăng nhập giải pháp như Prikka's, nhưng tôi làm tất cả trong Rack thay vì tạo Bộ điều khiển và tuyến mới.

# 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 giải pháp:

Không làm việc với Capybara, trừ khi thích ứng nhỏ thực hiện:

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 

(tìm thấy ở đây: https://gist.github.com/484787)

+0

Cảm ơn bạn đã đóng góp giải pháp của mình trở lại đây một lần nữa, Điều này đã giúp tôi rất nhiều, nó hoạt động như một sự quyến rũ. – Pedro

+1

Đối với tôi, điều đó chỉ gây ra lỗi: 'phương thức undefined 'current_session' cho # ' Bạn có thể cụ thể hơn về vị trí trong ứng dụng bạn cần để đặt mã này không? – Ajedi32

0

Một biến thể nhẹ:

# 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

Tôi gặp lỗi này! 'undefined method 'post' cho # (NoMethodError)'. Có vẻ như chúng ta không thể đăng bài với Selenium, ít nhất. –

1

Tại sao bạn không sử dụng FactoryGirl hoặc (Fixjour hoặc Fabricator) với Devise (hoặc Authlogic) và SentientUser? Sau đó, bạn có thể chỉ cần đánh hơi người dùng nào đã đăng nhập!

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

@ Ajedi32 Tôi chạy vào cùng một vấn đề (phương pháp xác định 'current_session' cho Capybara :: RackTest :: Driver) và đặt này trong định nghĩa bước của tôi cố định các vấn đề đối với tôi:

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 

Trong hành động điều khiển của tôi, tôi gọi cookie [: stub_user_id], thay vì cookie_jar [: stub_user_id]

0

Sau nhiều tìm kiếm và lướt web, tôi cuối cùng đã chọn giải pháp đơn giản và rõ ràng.

Sử dụng cookie sẽ thêm hai vấn đề. Trước tiên, bạn có mã trong ứng dụng cụ thể để thử nghiệm và thứ hai có vấn đề là việc tạo cookie trong Dưa chuột là khó khăn khi sử dụng bất kỳ thứ gì ngoài kiểm tra giá. Có nhiều giải pháp khác nhau cho vấn đề cookie nhưng tất cả chúng đều có một chút khó khăn, một số giới thiệu mocks, và tất cả chúng là những gì tôi gọi là 'khéo léo'. Một giải pháp như vậy là here.

Giải pháp của tôi là như sau. Điều này đang sử dụng xác thực cơ bản HTTP nhưng nó có thể được tổng quát cho hầu hết mọi thứ.

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 những gì bao giờ xác thực bình thường ngoại trừ nó bỏ qua bất kỳ thời gian tiêu thụ các bộ phận. Trong trường hợp của tôi, xác thực thực đang sử dụng LDAP mà tôi muốn tránh.

Có ... nó hơi thô nhưng rõ ràng, đơn giản và rõ ràng. Và ... không có giải pháp nào khác mà tôi thấy là sạch hơn hoặc rõ ràng hơn.

Lưu ý, một tính năng là nếu user_name không phải là 'testuser', thì đường dẫn bình thường được thực hiện để chúng có thể được kiểm tra.

Hy vọng điều này sẽ giúp người khác ...

Các vấn đề liên quan