Как заставить SpecFlow ожидать исключения?
Я использую SpecFlow, и я бы хотел написать такой сценарий, как:
Scenario: Pressing add with an empty stack throws an exception
Given I have entered nothing into the calculator
When I press add
Then it should throw an exception
It calculator.Add()
, который собирается генерировать исключение, так как я могу справиться с этим в методе, отмеченном [Then]
?
Ответы
Ответ 1
Отличный вопрос. Я не эксперт по bdd или specflow, однако мой первый совет - сделать шаг назад и оценить ваш сценарий.
Вы действительно хотите использовать термины "throw" и "exception" в этой спецификации? Имейте в виду, что идея с bdd - использовать вездесущий язык для бизнеса. В идеале они должны уметь читать эти сценарии и интерпретировать их.
Подумайте об изменении своей фразы "then", чтобы включить что-то вроде этого:
Scenario: Pressing add with an empty stack displays an error
Given I have entered nothing into the calculator
When I press add
Then the user is presented with an error message
Исключение все еще бросается в фоновом режиме, но конечным результатом является простое сообщение об ошибке.
Скотт Беллоуш затрагивает эту концепцию в этом подкасте "Колебания": http://herdingcode.com/?p=176
Ответ 2
Как новичок в SpecFlow, я не буду говорить вам, что это способ сделать это, но одним из способов сделать это было бы использовать ScenarioContext
для хранения исключения, созданного в Когда;
try
{
calculator.Add(1,1);
}
catch (Exception e)
{
ScenarioContext.Current.Add("Exception_CalculatorAdd", e);
}
В Затем вы можете проверить возникшее исключение и сделать на нем подтверждения;
var exception = ScenarioContext.Current["Exception_CalculatorAdd"];
Assert.That(exception, Is.Not.Null);
Сказанное; Я согласен с scoarescoare, когда он говорит, что вы должны сформулировать сценарий в немного более "удобных для бизнеса" формулировках. Тем не менее, использование SpecFlow для управления реализацией вашей модели домена, может быть полезно при использовании исключений и выполнения утверждений.
Btw: Отъезд Rob Conery screencast на TekPub для некоторых действительно хороших советов по использованию SpecFlow: http://tekpub.com/view/concepts/5
Ответ 3
BDD можно практиковать на уровне уровня поведения или/и на уровне уровня.
SpecFlow - это инструмент BDD, который фокусируется на поведении на уровне объектов.
Исключения - это не то, что вы должны указать/наблюдать за поведением на уровне объектов.
Исключения должны указываться/наблюдаться при поведении на уровне единицы.
Подумайте о сценариях SpecFlow в качестве реальной спецификации для нетехнического участника. Вы также не должны писать в спецификации, что генерируется исключение, но как система ведет себя в таком случае.
Если у вас нет каких-либо нетехнических заинтересованных сторон, то SpecFlow - неправильный инструмент для вас! Не тратьте энергию на создание бизнес-удобочитаемых спецификаций, если их никто не интересует.
Существуют инструменты BDD, которые фокусируются на поведении на уровне единицы. В .NET наиболее популярным является MSpec (http://github.com/machine/machine.specifications).
BDD на уровне единицы также может быть легко практикой со стандартными модульными модулями тестирования.
Тем не менее, вы все еще можете проверить исключение в SpecFlow.
Вот еще несколько обсуждений bdd на уровне единицы vs. bdd на уровне функции:
SpecFlow/BDD vs Unit Testing
BDD для тестов приемочного тестирования против BDD для модульных тестов (или: ATDD против TDD)
Также посмотрите этот пост в блоге:
Классификация BDD-инструментов (управляемый модулем и приемочным тестированием) и немного истории BDD
Ответ 4
Изменение сценария без исключения - это, вероятно, хороший способ ориентировать сценарий на пользователя. Однако, если вам все еще нужно, чтобы он работал, учтите следующее:
-
Поймайте исключение (я действительно рекомендую улавливать определенные исключения, если вам действительно не нужно поймать все) на этапе, который вызывает операцию и передает ее в контекст сценария.
[When("I press add")]
public void WhenIPressAdd()
{
try
{
_calc.Add();
}
catch (Exception err)
{
ScenarioContext.Current[("Error")] = err;
}
}
-
Подтвердить, что это исключение хранится в контексте сценария
[Then(@"it should throw an exception")]
public void ThenItShouldThrowAnException()
{
Assert.IsTrue(ScenarioContext.Current.ContainsKey("Error"));
}
P.S. Это очень близко к одному из существующих ответов. Однако, если вы попытаетесь получить значение из ScenarioContext, используя синтаксис, как показано ниже:
var err = ScenarioContext.Current["Error"]
он выдает другое исключение в случае, если ключ "Ошибка" не существует (и это приведет к сбою всех сценариев, которые выполняют вычисления с правильными параметрами). Поэтому ScenarioContext.Current.ContainsKey
может быть более подходящим
Ответ 5
В случае, если вы тестируете взаимодействие с пользователем, я буду только советовать, что уже было сказано о том, чтобы сосредоточиться на пользовательском опыте: "Тогда пользователю будет предоставлено сообщение об ошибке". Но, если вы тестируете уровень ниже пользовательского интерфейса, я хотел бы поделиться своим опытом:
Я использую SpecFlow для разработки бизнес-уровня. В моем случае я не забочусь о взаимодействиях пользовательского интерфейса, но я по-прежнему считаю чрезвычайно полезным подход BDD и SpecFlow.
В бизнес-слое мне не нужны спецификации, которые говорят "Тогда пользователю представлено сообщение об ошибке", но на самом деле проверка того, что служба правильно реагирует на неправильный ввод. Некоторое время я делал то, что уже было сказано о том, чтобы поймать исключение в "Когда" и проверить его на "Затем", но я считаю, что этот вариант не является оптимальным, потому что если вы повторно используете шаг "Когда", вы можете усвоить исключение, где вы этого не ожидали.
В настоящее время я использую явные предложения "Then", несколько раз без "Когда", таким образом:
Scenario: Adding with an empty stack causes an error
Given I have entered nothing into the calculator
Then adding causes an error X
Это позволяет мне конкретно кодировать действие и обнаружение исключений за один шаг. Я могу повторно использовать его для проверки количества ошибок, которые я хочу, и это не заставляет меня добавлять несвязанный код к неуспешным шагам "Когда".
Ответ 6
Мое решение включает в себя несколько элементов для реализации, но в самом конце это будет выглядеть намного элегантнее:
@CatchException
Scenario: Faulty operation throws exception
Given Some Context
When Some faulty operation invoked
Then Exception thrown with type 'ValidationException' and message 'Validation failed'
Чтобы выполнить эту работу, выполните следующие три шага:
Шаг 1
Отметьте сценарии, в которых вы ожидаете исключений с помощью некоторого тега, например. @CatchException
:
@CatchException
Scenario: ...
Шаг 2
Определите обработчик AfterStep
, чтобы изменить ScenarioContext.TestStatus
на OK
. Вам может потребоваться только игнорировать ошибки для шагов "Когда", так что вы все равно можете пропустить тест в Then, проверяя исключение. Должно было сделать это через отражение, поскольку свойство TestStatus
является внутренним:
[AfterStep("CatchException")]
public void CatchException()
{
if (ScenarioContext.Current.StepContext.StepInfo.StepDefinitionType == StepDefinitionType.When)
{
PropertyInfo testStatusProperty = typeof(ScenarioContext).GetProperty("TestStatus", BindingFlags.NonPublic | BindingFlags.Instance);
testStatusProperty.SetValue(ScenarioContext.Current, TestStatus.OK);
}
}
Шаг 3
Подтвердите TestError
так же, как вы бы подтвердили что-либо в ScenarioContext
.
[Then(@"Exception thrown with type '(.*)' and message '(.*)'")]
public void ThenExceptionThrown(string type, string message)
{
Assert.AreEqual(type, ScenarioContext.Current.TestError.GetType().Name);
Assert.AreEqual(message, ScenarioContext.Current.TestError.Message);
}