【问题标题】:How do I get SpecFlow to expect an exception?如何让 SpecFlow 期待异常?
【发布时间】:2011-02-22 06:22:23
【问题描述】:

我正在使用 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]的方法中该如何处理呢?

【问题讨论】:

  • 嘿,您觉得这些答案有用吗?
  • @scoarescoare:是的。问题是包含所有必需信息的正确答案是您和 Kjetil 的组合。你的回答说我的语言是错误的,而 Kjetil 实际上说如何从WhenThen 获取异常(或其他输出)。
  • 感谢您提出这个问题。我发现自己也在想同样的事情!

标签: specflow expected-exception


【解决方案1】:

很好的问题。我既不是 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

【讨论】:

  • 我要补充一点,像 specflow 这样的 BDD 工具意味着要与 TDD 结合使用。因此,您可以像这样编写规范,然后编写一个预期异常的单元测试。
  • 很好的答案!我也不是专家,但似乎是适当的回应。
【解决方案2】:

作为 SpecFlow 的新手,我不会告诉您这是 方法,但一种方法是使用 ScenarioContext 来存储抛出的异常何时

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

【讨论】:

  • 在 specflow 1.7.1.0 中,您可以参考 ScenarioContext.Current.TestError 以了解在场景期间捕获的异常。
  • 在我的上下文中,我有两种类型的 Whens :抛出异常的正常类型 When I press Add 和一种可以处理异常的类型:When I try to press Add 调用相同的 WhenIPressAdd() 方法但被包围使用try/catch 块并按照您的建议进行处理。现在系统可以向我抛出错误,我可以按需捕获和处理它们。
  • 我在业务规则级别进行单元测试,哪些异常将被抛出并且永远不会被捕获。 ——它会在上一层被捕获。所以你的解决方案对我非常有用。谢谢!
  • @Kjetil Klaussen,这种方法在近八年后仍然适用吗? +1。
  • @w0051977 是的,在这种特殊情况下是这样。但又一次;你真的不应该写这样的场景。这是一种反模式,因为在编写场景时应该考虑到最终用户。并且没有最终用户会希望抛出异常......
【解决方案3】:

BDD 可以在功能级别行为或/和单元级别行为上进行。

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

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

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

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

也就是说,你could still check for an exception in SpecFlow

以下是关于单元级别的 bdd 与功能级别的 bdd 的更多讨论: SpecFlow/BDD vs Unit Testing BDD for Acceptance Tests vs. BDD for Unit Tests (or: ATDD vs. TDD)

也看看这篇博文: Classifying BDD Tools (Unit-Test-Driven vs. Acceptance Test Driven) and a bit of BDD history

【讨论】:

  • 我理解 ATDD 和 TDD 之间的区别,如提到的博客文章中所述,但这让我提出了一个问题。如上所述,使用 BDD 工具(例如 MSpec)不只是另一个单元测试框架吗?在我看来是这样。此外,如果我可以对 ATDD 和 TDD 使用相同的工具,为什么不呢?这里似乎还有一些模糊的线条。
  • 嗨,Brian - 大多数 BDD 工具旨在帮助与非技术利益相关者达成共识。对于单元级别/技术利益相关者而言,没有那么多 BDD 工具,只是因为技术人员通常可以使用 TDD 框架进行单元级别 BDD 就好了。目前我对两者都使用 NUnit,并在下面使用英语风格的 DSL 来处理场景。如果它适合你,你可以这样做。唯一不同的是,我将场景步骤保持在高级别,以便我可以重用它们 - 重用远远大于单元级别。
  • Specflow 非常适合使用,即使查看测试的每个人都是纯技术人员。我和我的团队成员发现用 Specflow 编写的测试比其他测试更直观、更易于编写和重用。它们不会浪费能源。
【解决方案4】:

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

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

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

var err = ScenarioContext.Current["Error"]

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

【讨论】:

    【解决方案5】:

    我的解决方案涉及几个要实现的项目,但最终看起来会更加优雅:

    @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。您可能只想忽略 When 步骤中的错误,因此您仍然可以在 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);
    }
    

    【讨论】:

    • 在较新的版本中,TestStatus 属性更改为带有公共设置器的 ScenarioExecutionStatus(因此将来破坏更改的可能性较小),您现在可以按如下方式使用它:PropertyInfo testStatusProperty = typeof(ScenarioContext).GetProperty (nameof(ScenarioContext.Current.ScenarioExecutionStatus), BindingFlags.Public | BindingFlags.Instance); testStatusProperty.SetValue(ScenarioContext.Current, ScenarioExecutionStatus.OK);
    【解决方案6】:

    如果您正在测试用户交互,我只会建议已经说过的关于关注用户体验的内容:“然后会向用户显示错误消息”。但是,如果您正在测试低于 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
    

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

    【讨论】:

    • 我是 BDD 新手,但我真的不喜欢“在 When 中做某​​事并将其放入上下文中,然后在 Then 中读出”的模式。我认为随着规范数量的增加以及更多的规范被重用,这将越来越难以维护。我已经开始做你上面描述的事情,到目前为止我很喜欢它。
    【解决方案7】:

    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 已设置且属于您预期的异常类型。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-02-23
      • 2020-01-25
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多