42

我正在使用 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]呢?

4

7 回答 7

41

好问题。我既不是 bdd 也不是 specflow 专家,但是,我的第一个建议是退后一步并评估您的情况。

你真的想在这个规范中使用术语“抛出”和“异常”吗?请记住,使用 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

异常仍然在后台抛出,但最终结果是一条简单的错误消息。

Scott Bellware 在这个 Herding Code 播客中触及了这个概念:http ://herdingcode.com/?p=176

于 2010-05-21T23:59:07.180 回答
39

作为 SpecFlow 的新手,我不会告诉您这是执行此操作方法,但一种方法是使用用于存储在WhenScenarioContext中抛出的异常;

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 来驱动领域模型的实现、捕获异常并对它们进行断言会派上用场。

顺便说一句:查看 Rob Conery 在 TekPub 的截屏视频,了解有关使用 SpecFlow 的一些非常好的技巧:http: //tekpub.com/view/concepts/5

于 2010-05-23T13:05:01.467 回答
14

BDD 可以在特征级行为或/和单元级行为上实践。

SpecFlow 是一个专注于特征级别行为的 BDD 工具。异常不是您应该在功能级别行为上指定/观察的东西。应在单元级行为上指定/观察异常。

将 SpecFlow 场景视为非技术利益相关者的实时规范。您也不会在规范中写出抛出异常,而是在这种情况下系统的行为方式。

如果您没有任何非技术利益相关者,那么 SpecFlow 不适合您!如果没有人有兴趣阅读它们,请不要浪费精力来创建业务可读的规范!

有一些 BDD 工具专注于单元级别的行为。在 .NET 中,最流行的一种是 MSpec(http://github.com/machine/machine.specifications)。单元级别的 BDD 也可以很容易地成为标准单元测试框架的实践。

也就是说,您仍然可以在 SpecFlow 中检查异常

以下是关于单元级别的 bdd 与功能级别的 bdd 的更多讨论: SpecFlow/BDD 与单元测试 BDD 用于验收测试与 BDD 用于单元测试(或:ATDD 与 TDD)

还可以查看这篇博文: Classifying BDD Tools (Unit-Test-Driven vs. Acceptance Test Driven) 和一些 BDD 历史

于 2010-05-31T12:36:08.650 回答
7

将场景更改为没有异常可能是让场景更加面向用户的好方法。但是,如果您仍然需要让它工作,请考虑以下事项:

  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"));
    }
    

PS这非常接近现有答案之一。但是,如果您尝试使用以下语法从 ScenarioContext 获取值:

var err = ScenarioContext.Current["Error"]

如果“Error”键不存在,它将引发另一个异常(这将使所有使用正确参数执行计算的场景都失败)。所以ScenarioContext.Current.ContainsKey可能更合适

于 2010-07-07T00:02:52.553 回答
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'

要完成这项工作,请遵循以下 3 个步骤:

第1步

标记您希望在某些标记中出现异常的场景,例如@CatchException

@CatchException
Scenario: ...

第2步

定义AfterStep要更改ScenarioContext.TestStatus为的处理程序OK。您可能只想在 for When步骤中忽略错误,因此您仍然可以在Then verifying an exception 中使测试失败。必须通过反射来做到这一点,因为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);
}
于 2017-02-15T13:55:01.167 回答
5

如果您正在测试用户交互,我只会建议已经说过的关于关注用户体验的内容:“然后向用户显示错误消息”。但是,如果您正在测试低于 UI 的关卡,我想分享一下我的经验:

我正在使用 SpecFlow 开发业务层。就我而言,我不关心 UI 交互,但我仍然发现 BDD 方法和 SpecFlow 非常有用。

在业务层中,我不希望规范说“然后向用户显示错误消息”,而是实际验证服务是否正确响应错误输入。我已经做了一段时间了,已经说过在“When”捕获异常并在“Then”验证它,但我发现这个选项不是最佳的,因为如果你重用“When”步骤,你可能会吞下一个你没想到的例外。

目前,我正在使用显式的“Then”子句,有时没有“When”,这样:

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

这使我可以在一个步骤中专门对操作和异常检测进行编码。我可以重用它来测试尽可能多的错误案例,并且它不会让我将不相关的代码添加到非失败的“何时”步骤中。

于 2011-06-14T00:20:52.520 回答
1

ScenarioContext.Current 已被最新版本的 SpecFlow 弃用,现在建议在构造函数中的步骤测试类中添加一个 POCO,以存储/检索步骤之间的上下文,即

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

private ExceptionContext _context;

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

在你的 [When] 绑定中......

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

在您的 [Then] 绑定中,断言 _context.Exception 已设置并且是您期望的异常类型。

于 2020-07-13T14:46:29.100 回答