Есть ли эквивалент в RSpec для огурцов "Сценарии", или я использую RSpec неправильно?

Меня впечатляет краткость и полезность сценариев Cucumber, они отличный способ проверить нагрузку разных случаев.

например. пример сценария огурца

Feature: Manage Users
In order to manage user details
As a security enthusiast
I want to edit user profiles only when authorized

Scenario Outline: Show or hide edit profile link
  Given the following user records
    | username | password | admin |
    | bob      | secret   | false |
    | admin    | secret   | true  |
  Given I am logged in as "<login>" with password "secret"
  When I visit profile for "<profile>"
  Then I should <action>

  Examples:
    | login | profile | action                 |
    | admin | bob     | see "Edit Profile"     |
    | bob   | bob     | see "Edit Profile"     |
    |       | bob     | not see "Edit Profile" |
    | bob   | admin   | not see "Edit Profile" |

(Код, взятый из Райан Бэйтс Больше на огурцевом скринкасте)

Есть ли эквивалент в RSpec?

Я хотел бы сделать то же самое в RSpec и DRY мой код, уменьшив количество тестов до строки в таблице сценариев.

Хотя я мог написать код, чтобы сделать это сам, тот факт, что я рассматриваю его, заставляет меня задаться вопросом две вещи.

  • Если это полезно, возможно, он уже существует в этом случае, как его использовать?

  • Если он не существует, он предполагает, что это не должно быть сделано таким образом и что я неправильно подхожу к проблеме, как мне пересмотреть мой подход к RSpec?

Какой ответ правильный, и если он полезен, как мне это сделать?

Ответы

Ответ 1

Попробуйте использовать следующий подход. Мне нравится, как это получилось.

describe StateDateMethods do
  before :each do
    @product = OpenStruct.new
    @product.extend StateDateMethods
  end

  def parse_date(unparsed_date_value)
    unless unparsed_date_value.nil?
      DateTime.strptime(unparsed_date_value, '%m/%d/%Y')
    end
  end

  context '#pre_order?' do
    examples = [
      # [visible_on, pre_order_on, for_sale_on] => method_result
      { :inputs => [nil, nil, nil], :expected => false },
      { :inputs => ['1/1/2001', nil, nil], :expected => false },
      { :inputs => ['1/1/2001', '1/1/2001', nil], :expected => true },
      { :inputs => ['1/1/2001', '1/2/2001', nil], :expected => true },
      { :inputs => ['1/1/2001', '1/1/2001', '1/2/2001'], :expected => false },
      { :inputs => ['1/1/2001', '1/1/2001', '1/1/3001'], :expected => true },
      { :inputs => ['1/1/2001', '1/1/3001', '1/2/3001'], :expected => false },
      { :inputs => ['1/1/3001', '1/1/3001', '1/2/3001'], :expected => false },
      { :inputs => ['1/1/2001', nil, '1/1/2001'], :expected => false },
      { :inputs => ['1/1/2001', nil, '1/1/3001'], :expected => false }
    ]
    examples.each do |example|
      inputs = example[:inputs]

      it "should return #{example[:expected].inspect} when visible_on == #{inputs[0].inspect}, pre_order_on == #{inputs[1].inspect}, for_sale_on == #{inputs[2].inspect}" do
        @product.visible_on = parse_date(inputs[0])
        @product.pre_order_on = parse_date(inputs[1])
        @product.for_sale_on = parse_date(inputs[2])

        @product.pre_order?.should == example[:expected]
      end
    end
  end
end

Я думаю, что это дает лучшее из обоих миров, потому что это мешает мне повторять себя, и это создает другой тест для каждого условия.

Вот что выглядит сбой:

....F.....

Failures:

  1) StateDateMethods#pre_order? should return false when visible_on == "1/1/2001", pre_order_on == "1/1/2001", for_sale_on == "1/2/2001"
     Failure/Error: @product.pre_order?.should == example[:expected]
       expected: false
            got: true (using ==)
     # ./spec_no_rails/state_date_methods_spec.rb:40:in `block (4 levels) in <top (required)>'

Finished in 0.38933 seconds
10 examples, 1 failure

Failed examples:

rspec ./spec_no_rails/state_date_methods_spec.rb:35 # StateDateMethods#pre_order? should return false when visible_on == "1/1/2001", pre_order_on == "1/1/2001", for_sale_on == "1/2/2001"

А вот как выглядит все зеленые:

..........

Finished in 0.3889 seconds
10 examples, 0 failures

Ответ 3

Я бы не использовал RSpec таким образом. RSpec следует использовать для управления поведением в классе по одному небольшому поведению за раз. Поскольку каждое поведение является уникальным, вы должны использовать другой spec для его определения.

В приведенном выше сценарии у вас могут быть спецификации, определяющие поведение типа:

it "should allow user to edit his own profile"
it "should allow admin to edit other users profile"
it "should not allow non-admin to edit admin profile"
it "should not allow anonymous user to edit any profile"

Еще одна вещь - не рекомендуется использовать RSpec для управления несколькими уровнями приложения. Другими словами, когда вы определяете свои контроллеры, вы должны издеваться над взаимодействием с вашими моделями и т.д.

Ответ 4

Для табличного/параметризованного тестирования с RSpec теперь есть пара гемов, которые могут быть полезны:

  • RSpec :: WithParams - микро-DSL для параметризованного тестирования (раскрытие: я автор)
  • RSpec :: Parameterized - поддерживает простой параметризованный синтаксис теста в RSpec.