如何让 SpecFlow 预料到异常?

发布于 2024-09-02 13:00:11 字数 313 浏览 11 评论 0原文

我正在使用 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() 会抛出异常,那么如何在标记的方法中处理这个异常[然后]

I'm using SpecFlow, and I'd like to write a scenario such as the following:

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's calculator.Add() that's going to throw an exception, so how do I handle this in the method marked [Then]?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

倾城花音 2024-09-09 13:00:11

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

Great question. I am neither a bdd or specflow expert, however, my first bit of advice would be to take a step back and assess your scenario.

Do you really want to use the terms "throw" and "exception" in this spec? Keep in mind the idea with bdd is to use a ubiquitous language with the business. Ideally, they should be able to read these scenarios and interpret them.

Consider changing your "then" phrase to include something like this:

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

The exception is still thrown in the background but the end result is a simple error message.

Scott Bellware touches this concept in this Herding Code podcast: http://herdingcode.com/?p=176

浅紫色的梦幻 2024-09-09 13:00:11

作为 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

As a newbie to SpecFlow I won't tell you that this is the way to do it, but one way to do it would be to use the ScenarioContext for storing the exception thrown in the When;

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

In your Then you could check the thrown exception and do asserts on it;

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

With that said; I agree with scoarescoare when he says that you should formulate the scenario in a bit more 'business-friendly' wordings. However, using SpecFlow to drive the implementation of your domain-model, catching exceptions and doing asserts on them can come in handy.

Btw: Check out Rob Conery's screencast over at TekPub for some really good tips on using SpecFlow: http://tekpub.com/view/concepts/5

-小熊_ 2024-09-09 13:00:11

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

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

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

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

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

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

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

另请参阅这篇博文:
对 BDD 工具进行分类(单元测试驱动与测试驱动)验收测试驱动)和一些 BDD 历史

BDD can be practiced on feature level behavior or/and on unit level behavior.

SpecFlow is a BDD tool that focuses on feature level behavior.
Exceptions are not something that you should specify/observe on feature level behavior.
Exceptions should be specified/observed on unit-level behavior.

Think of SpecFlow scenarios as a live specification for the non technical stakeholder. You would also not write in the specification that an exception is thrown, but how the system behaves in such a case.

If you do not have any non technical stakeholders, then SpecFlow is the wrong tool for you! Don't waste energy in creating business readable specifications if there is nobody interested in reading them!

There are BDD tools that focus on unit level behavior. In .NET the most popular one is MSpec (http://github.com/machine/machine.specifications).
BDD on unit-level can also easily be practices with standard unit-testing frameworks.

That said, you could still check for an exception in SpecFlow.

Here are some more discussion of bdd on unit-level vs. bdd on feature-level:
SpecFlow/BDD vs Unit Testing
BDD for Acceptance Tests vs. BDD for Unit Tests (or: ATDD vs. TDD)

Also have look at this blog post:
Classifying BDD Tools (Unit-Test-Driven vs. Acceptance Test Driven) and a bit of BDD history

瀟灑尐姊 2024-09-09 13:00:11

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

  1. 在调用操作并将其传递给场景的步骤中捕获异常(我真的建议捕获特定异常,除非您确实需要捕获所有异常)上下文。

    [当("我按添加")]
    公共无效当IPressAdd()
    {
       尝试
       {
         _calc.Add();
       }
       捕获(异常错误)
       {
          ScenarioContext.Current[("错误")] = err;
       }
    }
    
  2. 验证异常是否存储在场景上下文中

    [Then(@"它应该抛出异常")]
    公共无效ThenItShouldThrowAnException()
    {
          Assert.IsTrue(ScenarioContext.Current.ContainsKey("错误"));
    }
    

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

var err = ScenarioContext.Current["Error"]

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

Changing the scenario not to have an exception is a probably good way to have the scenario more user oriented. However, if you still need to have it working, please consider the following:

  1. Catch an exception (I really recommend catching specific exceptions unless you really need to catch all) in the step that invokes an operation and pass it to the scenario context.

    [When("I press add")]
    public void WhenIPressAdd()
    {
       try
       {
         _calc.Add();
       }
       catch (Exception err)
       {
          ScenarioContext.Current[("Error")] = err;
       }
    }
    
  2. Validate that exception is stored in the scenario context

    [Then(@"it should throw an exception")]
    public void ThenItShouldThrowAnException()
    {
          Assert.IsTrue(ScenarioContext.Current.ContainsKey("Error"));
    }
    

P.S. It's very close to one of the existing answers. However, if you try getting value from ScenarioContext using syntax like below:

var err = ScenarioContext.Current["Error"]

it will throw another exception in case if "Error" key doesn't exist (and that will fail all scenarios that perform calculations with correct parameters). So ScenarioContext.Current.ContainsKey may be just more appropriate

↙温凉少女 2024-09-09 13:00:11

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

@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: ...

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

My solution involves a couple of items to implement, but at the very end it will look much more elegant:

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

To make this work, follow those 3 steps:

Step 1

Mark Scenarios you expect exceptions in with some tag, e.g. @CatchException:

@CatchException
Scenario: ...

Step 2

Define an AfterStep handler to change ScenarioContext.TestStatus to be OK. You may only want ignore errors in for When steps, so you can still fail a test in Then verifying an exception. Had to do this through reflection as TestStatus property is internal:

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

Step 3

Validate TestError the same way you would validate anything within 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);
}
污味仙女 2024-09-09 13:00:11

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

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

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

目前,我使用显式的“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

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

In case you are testing user interactions I will only advice what has already been said about focusing on the user experience: "Then the user is presented with an error message". But, in case you are testing a level below the UI, I'd like to share my experience:

I'm using SpecFlow to develop a business layer. In my case, I don't care about the UI interactions, but I still find extremely useful the BDD approach and SpecFlow.

In the business layer I don't want specs that say "Then the user is presented with an error message", but actually verifying that the service correctly responds to a wrong input. I've done for a while what has already been said of catching the exception at the "When" and verifying it at the "Then", but I find this option not optimal, because if you reuse the "When" step you could swallow an exception where you didn't expect it.

Currently, I'm using explicit "Then" clauses, some times without the "When", this way:

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

This allows me to specifically code the action and the exception detection in one step. I can reuse it to test as many error cases as I want and it doesn't make me add unrelated code to the non failing "When" steps.

仙女 2024-09-09 13:00:11

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

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

private ExceptionContext _context;

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

And 在您的 [When] 绑定中......

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

在您的[然后] 绑定,断言 _context.Exception 已设置并且属于您期望的异常类型。

ScenarioContext.Current is deprecated with the latest version of SpecFlow, it is now recommended to add a POCO to the steps test class in the constructor to store/retrieve context between steps, i.e.

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

private ExceptionContext _context;

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

And in your [When] binding....

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

In your [Then] binding, assert that _context.Exception is set and of the exception type you expected.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文