Утверждение, что конкретное исключение выбрасывается в Огурец

Сценарий

Я пишу библиотеку (без Ruby on Rails), для которой я хотел бы иметь очень подробные функции Cucumber. Это особенно касается описания ошибок/исключений, которые должны быть выбрасываться в различных случаях.

Пример

Самый интуитивный способ записи шагов Cucumber, вероятно, будет чем-то вроде

When I do something unwanted
Then an "ArgumentError" should be thrown

Проблема

Есть два вопроса, которые я должен решить:

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

Неудобное и громоздкое решение

Лучший подход, который я смог придумать, - кэшировать исключение на первом этапе и поместить его в переменную экземпляра, к которой может получить доступ второй шаг:

When /^I do something unwanted$/ do
  begin
    throw_an_exception!
  rescue => @error
  end
end

Then /^an "(.*)" should be thrown$/ do |error|
  @error.class.to_s.should == error
end

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

Итак, может ли кто-нибудь помочь мне с хотя бы менее громоздким решением? Или я должен писать свои функции по-другому? Любая помощь будет высоко оценена.

Ответы

Ответ 1

Я думал об этом еще раз, и, может быть, ответ таков:

Нет элегантного решения, потому что Given-When-Then -Scheme нарушается в вашем случае. Вы ожидаете, что "Тогда следует исключить исключение" - это результат "Когда я делаю что-то нежелательное".

Но когда вы думаете об этом, это не так! Исключение не является результатом этого действия, ведь исключение просто показывает, что ошибка "Когда" завершилась.

Моим решением для этого было бы тестирование на более высоком уровне:

When I do something unwanted
Then an error should be logged

или

When I do something unwanted
Then the user should get an error message

или

When I do something unwanted
Then the program should be locked in state "error"

или их комбинацию.

Затем вы будете "кэшировать исключение" в своей программе, что имеет смысл, поскольку вам, скорее всего, все равно придется это делать.

Две проблемы, о которых вы говорили, также будут решены.

Если вам действительно нужно проверить исключения

Ну, я думаю, тогда огурец - это не правильный набор тестов, хм?; -)

Так как схема "все-когда-то-то" нарушена, я просто напишу

When I do something unwanted it should fail with "ArgumentError"

и в определениях шагов что-то вроде (непроверенный, пожалуйста, исправьте меня, если вы попробуете)

When /^I do something unwanted it should fail with "(.*)"$/ do |errorstring|
  expect {
    throw_an_exception!
  }.to raise_error(errorstring)
end

Как сказано выше, это ужасно неправильно, поскольку схема нарушена, но она будет служить цели, не так ли?; -)

Вы найдете дополнительную документацию при ошибках тестирования в ожиданиях rspec.

Ответ 2

Один из вариантов - отметить сценарий с помощью @allow-rescue и проверить вывод страницы и код состояния. Например

В my_steps.rb

Then(/^the page (?:should have|has) content (.+)$/) do |content|
  expect(page).to have_content(content)
end

Then(/^the page should have status code (\d+)$/) do |status_code|
  expect(page.status_code.to_s).to eq(status_code)
end

Then /^I should see an error$/ do
  expect(400..599).to include(page.status_code)
end

В my_feature.feature

@allow-rescue
Scenario: Make sure user can't do XYZ
  Given some prerequisite
  When I do something unwanted
  Then the page should have content Routing Error
  And the page should have status code 404

или, альтернативно:

@allow-rescue
Scenario: Make sure user can't do XYZ
  Given some prerequisite
  When I do something unwanted
  Then I should see an error

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

Ответ 3

Можно создать исключение в блоке When, а затем сделать утверждения об этом в следующих блоках Then.

Используя ваш пример:

When /^I do something unwanted$/ do
  @result = -> { throw_an_exception! }
end

Then /^an "(.*)" should be thrown$/ do |error|
  expect{ @result.call }.to raise_error(error)
end

В этом примере используются RSpec, но важной частью является -> (Lambda); который позволяет передавать ссылку на метод throw_an_exception!.

Я надеюсь, что это поможет!

Ответ 4

Я отвечаю с точки зрения того, кто использует функции Огурца в ситуации, управляемой поведением, поэтому возьмите его или оставьте его...

Сценарии должны быть написаны для проверки "функции" или функциональности приложения, а не для проверки самого кода. Пример:

When the service is invoked
Then a success code should be returned

Это звучит как ваш тестовый пример (т.е. если я это сделаю, то это исключение должно быть брошено) является кандидатом на модульное тестирование или интеграционное тестирование - в моем случае мы будем использовать некоторую систему Mocking или unit testing.

Мое предложение состояло в том, чтобы переоценить сценарии функций, чтобы проверить, действительно ли они тестируют то, что вы намерены тестировать. Из личного опыта я обнаружил, что если мои тестовые классы становятся ненормально сложными, то мои функции "неправильны".