Как заставить 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

Исключение будет генерировать calculator.Add(), так как мне обработать это в методе с пометкой [Then]?


person Roger Lipscombe    schedule 21.05.2010    source источник
comment
Эй, вы нашли какой-либо из этих ответов полезным?   -  person Scott Coates    schedule 28.09.2011
comment
@scoarescoare: Да. Проблема в том, что правильный ответ, содержащий всю необходимую информацию, является комбинацией вашего ответа и ответа Кьетиля. Ваш ответ говорит, что мой язык неверен, а Кжетил на самом деле говорит, как получить исключение (или другой вывод) от When до Then.   -  person Roger Lipscombe    schedule 28.09.2011
comment
Спасибо, что спросили об этом. Я обнаружил, что задаюсь вопросом о том же!   -  person K-Dawg    schedule 03.09.2015


Ответы (7)


Отличный вопрос. Я не эксперт по bdd или specflow, однако мой первый совет — сделать шаг назад и оценить ваш сценарий.

Вы действительно хотите использовать термины «бросок» и «исключение» в этой спецификации? Имейте в виду, что идея с bdd состоит в том, чтобы использовать вездесущий язык с бизнесом. В идеале они должны уметь читать эти сценарии и интерпретировать их.

Попробуйте изменить фразу «тогда», включив в нее что-то вроде этого:

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

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

Скотт Беллвер затрагивает эту концепцию в подкасте Herding Code: http://herdingcode.com/?p=176

person Scott Coates    schedule 21.05.2010
comment
Я бы добавил, что инструменты BDD, такие как specflow, предназначены для использования в сочетании с TDD. Итак, вы пишете свою спецификацию таким образом, а затем пишете модульный тест, ожидающий исключения. - person thitemple; 14.06.2011
comment
Отличный ответ! Я тоже не эксперт, но это кажется подходящим ответом. - person K-Dawg; 03.09.2015

Как новичок в SpecFlow, я не скажу вам, что это самый способ сделать это, но один из способов сделать это — использовать ScenarioContext для хранения исключения, созданного в When ;

try
{
    calculator.Add(1,1);
}
catch (Exception e)
{
    ScenarioContext.Current.Add("Exception_CalculatorAdd", e);
}

В вашем Then вы можете проверить выброшенное исключение и выполнить для него утверждения;

var exception = ScenarioContext.Current["Exception_CalculatorAdd"];
Assert.That(exception, Is.Not.Null);

С учетом сказанного; Я согласен с scoarescoare, когда он говорит, что вы должны сформулировать сценарий в несколько более «дружественных для бизнеса» формулировках. Тем не менее, использование SpecFlow для управления реализацией вашей модели предметной области, перехват исключений и выполнение над ними утверждений может оказаться полезным.

Кстати: посмотрите скринкаст Роба Конери на TekPub, где вы найдете несколько действительно полезных советов по использованию SpecFlow: http://tekpub.com/view/concepts/5

person Kjetil Klaussen    schedule 23.05.2010
comment
В specflow 1.7.1.0 вы можете ссылаться на ScenarioContext.Current.TestError для исключений, обнаруженных во время сценария. - person Scott Coates; 29.09.2011
comment
В моем контексте у меня есть два типа Whens : нормальный, который генерирует исключения, такие как When I press Add, и тот, который может обрабатывать исключения: When I try to press Add, который вызывает тот же метод WhenIPressAdd(), но окружен блоком try/catch и обрабатывает, как вы предлагаете. Теперь система может выдавать мне ошибки, а я могу их ловить и обрабатывать по требованию. - person AutomatedChaos; 25.07.2013
comment
Я выполняю модульное тестирование на уровне бизнес-правил, какие исключения будут генерироваться и никогда не перехватываться. -- он будет пойман на один уровень выше. Так что ваше решение очень полезно для меня. Спасибо! - person user3381672; 14.08.2014
comment
@Kjetil Klaussen, подходит ли этот подход спустя почти восемь лет? +1. - person w0051977; 15.04.2018
comment
@ w0051977 да, в данном конкретном случае было бы так. Но опять же; вам действительно не следует писать такие сценарии. Это анти-шаблон, потому что сценарии должны быть написаны с учетом конечного пользователя. И ни один конечный пользователь никогда не захочет, чтобы было выброшено исключение... - person Kjetil Klaussen; 16.04.2018
comment
@Kjetil Klaussen, спасибо. Вы говорите, что ошибки проверки не следует тестировать для использования Specflow? Я тестирую их также с помощью BDD. - person w0051977; 16.04.2018
comment
@ w0051977 Нет, конечно; проверки должны быть проверены. Я просто имел в виду, что вы должны тестировать их в соответствии с тем, что должен ожидать конечный пользователь. Конечным результатом исключения может быть ошибка проверки, против которой вы должны выявлять в сценарии. - person Kjetil Klaussen; 17.04.2018

BDD можно практиковать на поведении на уровне функций и/или на уровне единиц.

SpecFlow — это инструмент BDD, который фокусируется на поведении на уровне функций. Исключения — это не то, что вы должны указывать/наблюдать за поведением на уровне функций. Исключения должны указываться/наблюдаться в поведении на уровне модуля.

Воспринимайте сценарии SpecFlow как живую спецификацию для нетехнических заинтересованных сторон. Вы бы еще написали в спецификации не то, что выбрасывается исключение, а то, как система ведет себя в таком случае.

Если у вас нет заинтересованных лиц, не являющихся техническими специалистами, то SpecFlow вам не подходит! Не тратьте энергию на создание бизнес-читаемых спецификаций, если никто не заинтересован в их чтении!

Существуют инструменты BDD, ориентированные на поведение на уровне юнитов. В .NET наиболее популярным является MSpec (http://github.com/machine/machine.specifications). BDD на уровне модулей также можно легко использовать со стандартными средами модульного тестирования.

При этом вы по-прежнему можете проверить наличие исключения в SpecFlow.

Вот еще несколько обсуждений bdd на уровне модуля и bdd на уровне функций: SpecFlow/BDD и модульное тестирование BDD для приемочных тестов и BDD для Модульные тесты (или: ATDD против TDD)

Также посмотрите этот пост в блоге: Классификация инструментов BDD (Unit-Test-Driven vs. Acceptance Test Driven) и немного истории BDD

person jbandi    schedule 31.05.2010
comment
Я понимаю различие между ATDD и TDD, как описано в упомянутом сообщении в блоге, но это приводит меня к вопросу. Как уже говорилось, не является ли использование инструмента BDD (например, MSpec) просто еще одной структурой модульного тестирования? Мне кажется, что да. Кроме того, если я могу использовать один и тот же инструмент как для ATDD, так и для TDD, почему бы и нет? Кажется, здесь все еще есть размытые линии. - person Brian McCord; 27.07.2010
comment
Привет, Брайан. Большинство инструментов BDD предназначены для того, чтобы помочь получить общее понимание с заинтересованными сторонами, не являющимися техническими специалистами. Существует не так много инструментов BDD для уровня подразделения / технических заинтересованных сторон, просто потому, что технические специалисты обычно могут прекрасно выполнять BDD на уровне подразделения с помощью TDD-фреймворков. Я использую NUnit для обоих на данный момент, с DSL в английском стиле внизу для сценариев. Вы можете сделать это, если это работает для вас. Единственное, что я делаю по-другому, — это сохраняю этапы сценария на высоком уровне, чтобы я мог их повторно использовать — повторное использование намного больше, чем на уровне модуля. - person Lunivore; 17.10.2010
comment
Specflow идеально подходит для использования, даже если все, кто смотрит на тесты, являются чисто техническими. Члены моей команды и я находим тесты, написанные в Specflow, более интуитивными и простыми в написании и повторном использовании, чем другие. Они НЕ являются пустой тратой энергии. - person MoonStom; 19.02.2015

Изменение сценария таким образом, чтобы не было исключений, вероятно, является хорошим способом сделать сценарий более ориентированным на пользователя. Однако, если вам все еще нужно, чтобы он работал, учтите следующее:

  1. Перехватите исключение (я действительно рекомендую перехватывать определенные исключения, если вам действительно не нужно перехватывать все) на шаге, который вызывает операцию, и передает его в контекст сценария.

    [When("I press add")]
    public void WhenIPressAdd()
    {
       try
       {
         _calc.Add();
       }
       catch (Exception err)
       {
          ScenarioContext.Current[("Error")] = err;
       }
    }
    
  2. Убедитесь, что исключение хранится в контексте сценария

    [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 может быть просто более подходящим

person Jake Starr    schedule 07.07.2010

Мое решение включает в себя пару пунктов для реализации, но в самом конце оно будет выглядеть намного элегантнее:

@CatchException
Scenario: Faulty operation throws exception
    Given Some Context
    When Some faulty operation invoked
    Then Exception thrown with type 'ValidationException' and message 'Validation failed'

Чтобы это сработало, выполните следующие 3 шага:

Шаг 1

Отметьте сценарии, в которых вы ожидаете исключения, с помощью некоторого тега, например. @CatchException:

@CatchException
Scenario: ...

Шаг 2

Определите обработчик AfterStep, чтобы изменить ScenarioContext.TestStatus на OK. Вы можете захотеть игнорировать ошибки только для шагов Когда, чтобы вы все равно могли провалить тест в Затем, проверяя исключение. Пришлось сделать это через отражение, так как свойство 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);
}
person Vitaliy Ulantikov    schedule 15.02.2017
comment
В более новых версиях свойство TestStatus было изменено на ScenarioExecutionStatus с общедоступным установщиком (чтобы меньше шансов нарушить изменения в будущем), теперь вы можете использовать его следующим образом: ), BindingFlags.Public | BindingFlags.Instance); testStatusProperty.SetValue(ScenarioContext.Current, ScenarioExecutionStatus.OK); - person Rob; 24.05.2018

В случае, если вы тестируете взаимодействие с пользователем, я только посоветую то, что уже было сказано о сосредоточении внимания на пользовательском опыте: «Затем пользователю предоставляется сообщение об ошибке». Но, если вы тестируете уровень ниже пользовательского интерфейса, я хотел бы поделиться своим опытом:

Я использую SpecFlow для разработки бизнес-уровня. В моем случае меня не волнуют взаимодействия с пользовательским интерфейсом, но я все же нахожу чрезвычайно полезным подход BDD и SpecFlow.

На бизнес-уровне мне не нужны спецификации, которые говорят: «Затем пользователю предоставляется сообщение об ошибке», но на самом деле проверяют, правильно ли служба реагирует на неправильный ввод. Я некоторое время делал то, что уже было сказано о перехвате исключения в «Когда» и проверке его в «Тогда», но я считаю этот вариант не оптимальным, потому что, если вы повторно используете шаг «Когда», вы можете проглотить исключение, когда вы этого не ожидали.

В настоящее время я использую явные предложения «Тогда», иногда без «Когда», таким образом:

Scenario: Adding with an empty stack causes an error
     Given I have entered nothing into the calculator
     Then adding causes an error X

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

person Francesc Castells    schedule 14.06.2011
comment
Я новичок в BDD, но мне очень не нравится шаблон «сделать что-то в «когда» и бросить это в контексте, а затем прочитать это в «тогда». Я думаю, что это будет все труднее и труднее поддерживать по мере роста числа спецификаций и их повторного использования. Я начал делать то, что вы описали выше, и пока мне это нравится. - person Seth Petry-Johnson; 24.01.2012

ScenarioContext.Current устарел в последней версии SpecFlow, теперь рекомендуется добавить POCO в тестовый класс шагов в конструкторе для хранения/получения контекста между шагами, т.е.

public class ExceptionContext
{
    public Exception Exception { get; set; }
}

private ExceptionContext _context;

public TestSteps(ExceptionContext context)
{
    _context = context;
}

И в вашей [Когда] привязке....

try
{
   // do something
}
catch (MyException ex)
{
    _context.Exception = ex;
}

В вашей привязке [Then] подтвердите, что _context.Exception установлен и имеет тип исключения, который вы ожидали.

person DaveRead    schedule 13.07.2020