2010-05-21 53 views
36

我使用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]

+0

嘿,你有没有发现这些答案有用? – 2011-09-28 00:22:43

+0

@scoarescoare:是的。问题是,包含所有必需信息的正确答案是您和Kjetil的组合。你的回答说我的语言是错误的,而Kjetil实际上是说如何从'When'到Then的异常(或其他输出)。 – 2011-09-28 08:05:17

+0

感谢您提出这个问题。我发现自己想知道同样的事情! – 2015-09-03 11:03:00

回答

38

伟大的问题。我既不是bdd或specflow专家,但是,我的第一点建议是退后一步,评估你的情况。

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

异常仍甩在了背景,但最终的结果是一个简单的错误消息。

斯科特Bellware触及这个概念在这羊群代码播客:http://herdingcode.com/?p=176

+13

我想补充一点,像specflow这样的BDD工具意味着与TDD结合使用。所以,你这样写你的规格,然后你会写一个单元测试,期望有一个异常。 – vintem 2011-06-14 18:52:22

+0

很好的回答!我也不是专家,但似乎是合适的回应。 – 2015-09-03 11:04:43

33

作为一个新手到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:退房罗布科纳的截屏在在TekPub有关使用SpecFlow一些很好的建议:http://tekpub.com/view/concepts/5

+2

在specflow 1.7.1.0中,您可以引用ScenarioContext.Current.TestError来查找场景期间捕获的异常。 – 2011-09-29 16:38:35

+0

在我的上下文中,我有两种类型的'When's:抛出异常的正常情况如'当我按下Add'和一个可以处理异常的异常:'当我尝试按Add时调用相同的'WhenIPressAdd()'方法,但用'try' /'catch'块包围并像你所建议的那样处理。现在系统可以向我发出错误信息,我可以根据需要捕捉和处理它们。 – AutomatedChaos 2013-07-25 11:04:51

+0

我在业务规则级别进行单元测试,该异常将被抛出并且从未被捕获。 - 它会被抓到上面的一个级别。所以你的解决方案对我来说非常有用。谢谢! – user3381672 2014-08-13 20:31:57

12

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

+0

所有好东西,谢谢。 – 2010-05-31 12:54:17

+0

我了解ATDD和TDD之间的区别,如所述的博客文章中所述,但这引起我一个问题。如上所述,是不是使用BDD工具(如MSpec)就是另一个单元测试框架?在我看来,这是。此外,如果我可以对ATDD和TDD使用相同的工具,为什么不呢?这里似乎还有一些模糊的线条。 – 2010-07-27 15:07:33

+0

嗨布莱恩 - 大多数BDD工具旨在帮助与非技术利益相关者达成共识。单元级/技术利益相关者没有太多的BDD工具,只是因为技术人员通常可以在TDD框架下进行单元级BDD。目前我使用的是NUnit,下方是英文风格的DSL。你可以做到这一点,如果它适合你。我做的唯一不同之处就是让场景步骤保持高水平,以便我可以重用它们 - 重用远远大于单元级别的重用。 – Lunivore 2010-10-16 21:59:06

6

改变的情况不有一个例外是让这个场景更加面向用户的好方法。然而,如果你仍然需要有它的工作,请考虑以下因素:

  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"] 

它会抛出另一个异常的情况下,如果“错误”项不存在(这将失败与正确的参数进行计算,所有场景)。所以ScenarioContext.Current.ContainsKey可能会更恰当

3

如果您正在测试用户交互,我只会建议已经说过关于用户体验的话:“然后用户会看到一条错误消息”。但是,如果您正在测试UI以下的级别,我想分享我的经验:

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

在业务层,我不希望规范说“然后用户出现错误消息”,但实际上验证服务正确地响应错误的输入。我已经完成了一段时间,在“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 

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

+1

我是BDD的新手,但我真的不喜欢“在某事中做某事并将其放在上下文中,然后在那里读出来”的模式。我认为随着规格数量的增长,这将变得越来越难以维持,并且更多的数据被重复使用。我已经开始做你上面描述的,到目前为止我喜欢它。 – 2012-01-24 13:10:42

1

我的解决方案涉及到几个项目来实现的,但在最后它看起来更优雅:

@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.TestStatusOK。您可能只想忽略错误步骤,所以你仍然可以通过测试失败,然后验证异常。已通过反射来做到这一点作为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); 
}