带有 Cucumber Stories 的会话变量

发布于 2024-08-01 16:32:24 字数 1018 浏览 4 评论 0原文

我正在为“注册”应用程序编写一些 Cucumber 故事,该应用程序有多个步骤。

我宁愿像普通用户一样完成控制器中的每个操作,而不是编写一个 Huuuuuuuge 故事来一次涵盖所有步骤(这会很糟糕)。 我的问题是,我将第一步创建的帐户 ID 存储为会话变量,因此当访问步骤 2、步骤 3 等时,会加载现有的注册数据。

我知道能够在 RSpec 规范中访问 controller.session[..] 但是,当我尝试在 Cucumber 故事中执行此操作时,它会失败并出现以下错误(并且,我还阅读了某处这是反模式等...):

使用controller.session[:whatever]或session[:whatever]

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

使用session(:whatever)

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

所以,它似乎访问会话存储实际上是不可能的。 我想知道是否有可能(我猜哪一个是最好的..):

  1. 模拟会话存储等
  2. 在控制器内有一个方法并存根该方法(例如 get_registration它分配了一个实例变量...)

我已经浏览了 RSpec 书(好吧,略读)并浏览了 WebRat 等,但我还没有真正找到我的问题的答案...

为了澄清一点 用户在注册完成之前要完成四个步骤 - 因此“登录”实际上并不是一个选项(它破坏了网站工作方式的模型)...

,注册过程更像是一个状态机 - 例如, 我的控制器规范我能够消除对基于会话变量加载模型的方法的调用 - 但我不确定“反模式”行是否也适用于存根和模拟?

谢谢!

I am working on some Cucumber stories for a 'sign up' application which has a number of steps.

Rather then writing a Huuuuuuuge story to cover all the steps at once, which would be bad, I'd rather work through each action in the controller like a regular user. My problem here is that I am storing the account ID which is created in the first step as a session variable, so when step 2, step 3 etc are visited the existing registration data is loaded.

I'm aware of being able to access controller.session[..] within RSpec specifications however when I try to do this in Cucumber stories it fails with the following error (and, I've also read somewhere this is an anti-pattern etc...):

Using controller.session[:whatever] or session[:whatever]

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

Using session(:whatever)

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

So, it seems accession the session store isn't really possible. What I'm wondering is if it might be possible to (and I guess which would be best..):

  1. Mock out the session store etc
  2. Have a method within the controller and stub that out (e.g. get_registration which assigns an instance variable...)

I've looked through the RSpec book (well, skimmed) and had a look through WebRat etc, but I haven't really found an answer to my problem...

To clarify a bit more, the signup process is more like a state machine - e.g. the user progresses through four steps before the registration is complete - hence 'logging in' isn't really an option (it breaks the model of how the site works)...

In my spec for the controller I was able to stub out the call to the method which loads the model based on the session var - but I'm not sure if the 'antipattern' line also applies to stubs as well as mocks?

Thanks!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(11

贪了杯 2024-08-08 16:32:24

我不知道这与原来的问题有多大关系,但我决定本着讨论的精神发布......

我们有一个黄瓜测试套件,需要> 运行时间为 10 分钟,因此我们想做一些优化。 在我们的应用程序中,登录过程会触发大量与大多数场景无关的额外功能,因此我们想通过直接设置会话用户 ID 来跳过这些功能。

Ryanb 的上述方法效果很好,只是我们无法使用该方法注销。 这导致我们的多用户故事失败。

我们最终创建了一个仅在测试环境中启用的“快速登录”路由:

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

这是创建会话变量的相应操作:

# 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

对于我们来说,这最终比使用 cookies 数组更简单。 作为奖励,这种方法也适用于 Selenium/Watir。

缺点是我们在应用程序中包含了与测试相关的代码。 就我个人而言,我不认为添加代码以使应用程序更具可测试性是一个巨大的罪恶,即使它确实增加了一些混乱。 也许最大的问题是未来的测试作者需要弄清楚他们应该使用哪种类型的登录。 由于硬件性能不受限制,我们显然不会这样做。

I don't know how much this relates to the original question anymore, but I decided to post anyway in the spirit of discussion...

We have a cucumber test suite that takes > 10 minutes to run so we wanted to do some optimization. In our app the login process triggers a LOT of extra functionality that is irrelevant to majority of the scenarios, so we wanted to skip that by setting the session user id directly.

Ryanb's approach above worked nicely, except that we were unable to log out using that approach. This made our multi-user stories fail.

We ended up creating a "quick login" route that is only enabled in test environment:

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

Here is the corresponding action that creates the session variable:

# 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

For us this ended up being simpler than working with the cookies array. As a bonus, this approach also works with Selenium/Watir.

Downside is that we're including test-related code in our application. Personally I don't think that adding code to make application more testable is a huge sin, even if it does add a bit of clutter. Perhaps the biggest problem is that future test authors need to figure out which type of login they should use. With unlimited hardware performance we obviously wouldn't be doing any of this.

提笔落墨 2024-08-08 16:32:24

关于。 Ryan 的解决方案 - 您可以在 env.rb 文件中打开 ActionController 并将其放置在那里,以避免放入您的生产代码库(感谢 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

Re. Ryan's solution - you can open up ActionController in you env.rb file and place it there to avoid putting in your production code base (thanks to 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
生活了然无味 2024-08-08 16:32:24

我将重复 danpickett 所说的,在 Cucumber 中应尽可能避免模拟。 但是,如果您的应用程序没有登录页面,或者性能可能存在问题,那么可能需要直接模拟登录。

这是一个丑陋的黑客,但它应该可以完成工作。

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

I'll repeat danpickett in saying mocks should be avoided whenever possible in Cucumber. However if your app does not have a login page, or perhaps performance is a problem, then it may be necessary to simulate login directly.

This is an ugly hack, but it should get the job done.

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
旧时浪漫 2024-08-08 16:32:24

模拟在黄瓜场景中很糟糕——它们几乎是一种反模式。

我的建议是编写一个实际登录用户的步骤。我这样做

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

我意识到实例变量 @user 是一种不好的形式 - 但我认为在登录/的情况下出来,拥有 @user 绝对有帮助。

有时我称之为@current_user

mocks are bad in cucumber scenarios - they're almost kind of an antipattern.

My suggestion is to write a step that actually logs a user in. I do it this way

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

I realize that the instance variable @user is kind of bad form—but I think in the case of logging in/out, having @user is definitely helpful.

Sometimes I call it @current_user.

茶底世界 2024-08-08 16:32:24

我的理解是,您会得到:

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

在实例化请求之前访问 session[] 时。 就您而言,我想如果您在步骤定义中访问 session[] 之前放置 webrats' visit some_existing_path ,错误就会消失。

现在,不幸的是,会话似乎并没有跨步骤持续存在(至少,我找不到方法),所以这些信息无助于回答你的问题:)

所以,我想,Ryan 的 session[:user_id] = cookies[:stub_user_id]... 是要走的路。 尽管在我看来,在应用程序本身中测试相关代码听起来并不正确。

My understanding is that you get:

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

when session[] is accessed before request has been instantiated. In your case, I'd imagine if you put webrats' visit some_existing_path before accessing session[] in your step defenition, the error will go away.

Now, unfortunately, session doesn't seem to persist across steps (at least, I couldn't find the way), so this bit of information doesn't help to answer your question :)

So, I suppose, Ryan's session[:user_id] = cookies[:stub_user_id]... is the way to go. Although, imo, test related code in the application itself doesn't sound right.

仄言 2024-08-08 16:32:24

回复:Ryan 的解决方案:

不适用于水豚,除非进行了小幅调整:(

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

此处找到:https://gist.github .com/484787

Re: Ryan's solution:

Does not work with Capybara, unless small adaptation done:

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

(found here: https://gist.github.com/484787)

十雾 2024-08-08 16:32:24

我使用仅测试登录解决方案,例如 Prikka's,但是我在 Rack 中完成这一切,而不是创建新的控制器和路由。

# 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)

I use a testing-only sign-in solution like Prikka's, but I do it all in Rack instead of creating a new Controller and routes.

# 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)
毁我热情 2024-08-08 16:32:24

@Ajedi32我遇到了同样的问题(Capybara::RackTest::Driver的未定义方法'current_session')并将其放入我的步骤定义中为我解决了这个问题:

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

在我的控制器操作中,我引用了cookies[:stub_user_id],而不是 cookie_jar[:stub_user_id]

@Ajedi32 I ran into the same issue (undefined method 'current_session' for Capybara::RackTest::Driver) and putting this in my step definition fixed the problem for me:

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 my controller action, I referred to cookies[:stub_user_id], instead of cookie_jar[:stub_user_id]

枫林﹌晚霞¤ 2024-08-08 16:32:24

为什么不将 FactoryGirl 或(Fixjour 或 Fabricator)与 Devise(或 Authlogic)和 SentientUser 结合使用? 然后你可以简单地嗅探哪个用户已经登录!

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

Why don't you use FactoryGirl or (Fixjour or Fabricator) with Devise (or Authlogic) and SentientUser? Then you can simply sniff which user is already logged in!

@user = Factory(:user)       # FactoryGirl
sign_in @user                # Devise
User.current.should == @user # SentientUser
归属感 2024-08-08 16:32:24

另一个细微的变化:

# 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

Another slight variation:

# 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
绿萝 2024-08-08 16:32:24

经过大量的灵魂搜索和网上冲浪后,我最终选择了一个非常简单且明显的解决方案。

使用 cookies 会带来两个问题。 首先,您在应用程序中有专门用于测试的代码,其次,存在的问题是,当使用除机架测试之外的任何内容时,在 Cucumber 中创建 cookie 很困难。 cookie 问题有多种解决方案,但都有点具有挑战性,有些引入了模拟,所有这些都是我所说的“棘手”。 一种这样的解决方案是这里< /a>.

我的解决方案如下。 这是使用 HTTP 基本身份验证,但它可以推广到大多数情况。

  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 执行正常身份验证所做的操作,除了它绕过任何耗时的部分。 就我而言,真正的身份验证是使用我想避免的 LDAP。

是的……虽然有点粗俗,但很清楚、简单、明显。 而且……我见过的没有其他解决方案比它更干净或更清晰。

请注意,一个功能是,如果 user_name 不是“testuser”,则采用正常路径,以便可以对其进行测试。

希望这对其他人有帮助...

After a lot of soul searching and web surfing, I finally opt'ed for a very simple and obvious solution.

Using cookies adds two problems. First you have code in the application specific for testing and second there is the problem that creating cookies in Cucumber is hard when using anything other than rack test. There are various solutions to the cookie problem but all of them are a bit challenging, some introduce mocks, and all of them are what I call 'tricky'. One such solution is here.

My solution is the following. This is using HTTP basic authentication but it could be generalized for most anything.

  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 does what ever the normal authenticate does except it bypasses any time consuming parts. In my case, the real authentication is using LDAP which I wanted to avoid.

Yes… it is a bit gross but it is clear, simple, and obvious. And… no other solution I've seen is cleaner or clearer.

Note, one feature is that if the user_name is not 'testuser', then the normal path is taken so they can be tested.

Hope this helps others...

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文