编写传递额外信息的 Cucumber 测试

Writing Cucumber tests that pass extra information

我在 Rails 上有一个 Ruby 程序,在 Cucumber 中进行了功能测试。

我刚刚实现了管理员可以为客户端用户创建新密码的功能。现在,在 "edit client" 页面上,有一个附加按钮允许管理员设置密码。现在,我只需要做一个黄瓜测试。

我试图将此基于客户端更改密码的正常测试,以及管理员更改用户信息的测试。我有的是:

Feature: Analyst changes client's password
  As an Analyst
  I want to change client's password
  So that I can reset the client's account

  Background:
    Given the following client accounts
      | email             | password |
      | user1@someorg.com | password |
    And I am logged in as an admin

  @javascript
  Scenario: Update a Client user
    Given I navigate to the Clients Management Page
    When I edit the Client User "user1@someorg.com"
    And I click on "button"
    Then I should be on the Clients Password Page

  @javascript
  Scenario: Can change password if confirmation matches
    Given I navigate to the Clients Password Page
    And I enter "Password1" as the password
    And I enter "Password1" as the password confirmation
    And I submit the form
    Then I should be taken to the Client Landing Page
    And The client's password should be "Password1"

在步骤中,我有:

Given /^I navigate to the Clients Password Page$/ do
  client_management_index_page = ClientsPasswordPage.new Capybara.current_session
  client_management_index_page.visit
end

Then /^I should be on the Clients Password Page$/ do
  client_password_page = ClientsPasswordPage.new Capybara.current_session
  expect(client_password_page).to be_current_page
end

和客户密码页面:

class ClientsPasswordPage
  include PageMixin
  include Rails.application.routes.url_helpers

  def initialize session
    initialize_page session, edit_admin_client_password_path
  end
end

除了 edit_admin_client_password_path 为正在编辑的用户取一个 :id。我不知道如何将这些信息输入其中。

以防万一,我正在使用 Devise 来处理安全问题...

有几种方法可以做到这一点。最简单的是意识到您在测试期间只创建了一个客户端,所以

Client.first # whatever class represents clients

永远是那个客户。显然,如果您在测试中创建的测试多于客户端,那么这将不起作用,因此您可以在黄瓜步骤中创建实例变量,这些实例变量在世界上设置,然后可以从其他步骤访问并传递给您的页面对象

When I edit the Client User "user1@someorg.com"
  @current_client = Client.find_by(email: "user1@someorg.com") # obviously would actually be a parameter to the step
  ...
end

Then /^I should be on the Clients Password Page$/ do
  client_password_page = ClientsPasswordPage.new Capybara.current_session, @current_client
  expect(client_password_page).to be_current_page
end

当然,如果没有页面对象开销,这只会变成

Then /^I should be on the Clients Password Page$/ do
  expect(page).to have_current_path(edit_admin_client_password_path(@current_client))
end

您可以采取多种措施来简化这种情况。如果您有更简单的场景,使用更简单的步骤定义,那么将更容易解决实现问题,例如如何在第一步中获得客户端以在第二步中可用。

简化场景的主要方法是在场景中完全不包含任何解释您如何实现功能的内容。如果您将所有点击按钮、填写字段和访问页面的操作都从您的场景中移除,您就可以专注于业务问题。

那么

Background
  Given there is a client
  And I am logged in as an admin

Scenario: Change clients password
  When I change the clients password
  Then the client should have a new password

注意:这立即提出了问题'How does the client find out about there new password?',这就是好的简单场景所做的,它们会让你提出有价值的问题。回答这个问题可能超出了这里的范围。

现在让我们看看实现。

Given 'there is a client' do
  @client = create_new_client
end

When 'I change the clients password' do
  visit admin_change_password_path(@client)
  change_client_password(client: @client)
end

这可能足以让您走上正确的道路。另外像

Given 'I am logged in as an admin' do
  @i = create_admin_user
  login_as(user: @i)
end

会有帮助。

我们在这里所做的是

  1. 将 HOW 向下推到堆栈中,这样现在您有权执行此操作的代码不在您的场景和步骤定义中
  2. 使用变量在步骤之间进行通信第 @client = create_new_client 行创建了一个全局变量(实际上是 Cucumber::World 的全局变量),该变量在所有步骤定义中都可用

您可以通过向 Cucumber 世界添加模块并在其中定义方法来创建辅助方法。请注意,这些方法是全局的,因此您必须仔细考虑名称(这些方法是全局的有很好的理由)。所以

module UserStepHelper
  def create_new_client
    ...
  end

  def create_admin_user
    ...
  end

  def change_client_password(client: )
    ...
  end
end
World UserStepHelper

将创建一个可以在任何步骤定义中使用的辅助方法。

您可以查看此方法的示例 here。我在 2013 年 CukeUp 的演讲中使用的一个项目。也许你可以用它作为你的教程示例。