我如何获得SpecFlow预期exception?
我正在使用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()
这将抛出一个exception,所以我如何处理标记为[Then]
的方法?
伟大的问题。 我既不是bdd或specflow专家,但是,我的第一点build议是退后一步,评估你的情况。
你真的想在这个规范中使用术语“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
exception仍然在后台抛出,但最终的结果是一个简单的错误信息。
Scott Bellware在这个Herding Code podcast中涉及到这个概念: http ://herdingcode.com/?p=176
作为SpecFlow的新手,我不会告诉你这是做到这一点的方法,但是一种方法是使用ScenarioContext
来存储在When中抛出的exception;
try { calculator.Add(1,1); } catch (Exception e) { ScenarioContext.Current.Add("Exception_CalculatorAdd", e); }
在你的然后你可以检查抛出的exception,并在它上面断言;
var exception = ScenarioContext.Current["Exception_CalculatorAdd"]; Assert.That(exception, Is.Not.Null);
照这样说; 我同意scoarescoare,当他说你应该制定一个更“商业友好”的措辞的情况。 但是,使用SpecFlow来驱动域模型的实现,捕获exception并在其上执行断言可能会派上用场。
顺便说一句:查看Rob Conery在TekPub上的截屏,了解使用SpecFlow的一些非常好的提示: http ://tekpub.com/view/concepts/5
BDD可以在特征级行为或/和单元级行为上实践。
SpecFlow是一个专注于function级别行为的BDD工具。 例外情况不是您应该在function级别行为中指定/观察的内容。 应该在单元级行为中指定/遵守例外情况。
将SpecFlow场景想象成非技术性利益相关者的实时规范。 您也不会在规范中写入抛出exception,而是在这种情况下系统的行为。
如果您没有任何非技术性的利益相关者,那么SpecFlow就是您的错误工具! 如果没有人有兴趣阅读,不要浪费能量来创build商业可读的规范!
有BDD工具专注于单元级别的行为。 在.NET中最受欢迎的是MSpec( http://github.com/machine/machine.specifications )。 单元级BDD也可以通过标准的unit testing框架轻松实现。
这就是说,你仍然可以在SpecFlow中检查exception 。
下面是关于单位水平上bdd和function水平bdd的更多讨论: SpecFlow / BDD与单位testing BDD验收testing与BDD单位testing(或:ATDD与TDD)
也看看这个博客文章: 分类BDD工具(unit testing驱动与验收testing驱动)和一点BDD的历史
改变场景不会有exception是让场景更加面向用户的好方法。 但是,如果您仍然需要使用它,请考虑以下事项:
-
在调用操作并将其传递给场景上下文的步骤中,捕获exception(我真的build议捕获特定的exception,除非您真的需要捕获所有exception)。
[When("I press add")] public void WhenIPressAdd() { try { _calc.Add(); } catch (Exception err) { ScenarioContext.Current[("Error")] = err; } }
-
validation该exception存储在场景上下文中
[Then(@"it should throw an exception")] public void ThenItShouldThrowAnException() { Assert.IsTrue(ScenarioContext.Current.ContainsKey("Error")); }
PS这是非常接近现有的答案之一。 但是,如果您尝试使用类似下面的语法从ScenarioContext获取值:
var err = ScenarioContext.Current["Error"]
它会抛出另一个exception,如果“错误”键不存在(这将失败所有使用正确的参数进行计算的情况下)。 所以ScenarioContext.Current.ContainsKey
可能更合适
如果您正在testing用户交互,我只会build议关注用户体验的内容:“然后用户会收到一条错误消息”。 但是,如果您正在testingUI以下的级别,我想分享我的经验:
我正在使用SpecFlow开发业务层。 就我而言,我并不关心UI交互,但我仍然发现BDD方法和SpecFlow非常有用。
在业务层,我不希望规范说“然后用户提供了错误消息”,但实际上validation服务正确地响应错误的input。 我已经做了一段时间了,已经说过在什么时候捕捉exception,然后在“Then”处进行validation,但是我发现这个选项并不是最优的,因为如果你重复使用“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
这允许我在一个步骤中专门编码操作和exception检测。 我可以重复使用它来testing尽可能多的错误情况,因为我不想让不相关的代码添加到不合格的“When”步骤中。
我的解决scheme涉及一些项目来实现,但最终会看起来更优雅:
@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
标记场景你期望与一些标签exception,例如@CatchException
:
@CatchException Scenario: ...
第2步
定义一个AfterStep
处理程序,将ScenarioContext.TestStatus
更改为OK
。 你可能只想忽略错误中的步骤,所以你仍然可以在一个testing中失败然后validation一个exception。 必须通过reflection来做到这一点,因为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步
以与在ScenarioContext
validation任何内容相同的方式validationTestError
。
[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); }