如何让 SpecFlow 预料到异常?
-
04-10-2019 - |
题
我正在使用 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]
?
解决方案
好问题。我既不是BDD或Specflow专家,但是,我的第一个建议是退后一步并评估您的情况。
您是否真的想在此规范中使用术语“投掷”和“异常”?请记住,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
该异常仍在背景中抛出,但最终结果是一个简单的错误消息。
Scott Bellware在此放牧代码播客中触及了这个概念: http://herdingcode.com/?p=176
其他提示
作为新手,我不会告诉你这是 这 做到这一点的方法,但是一种方法是使用 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);
照这样说;我同意 斯科雷斯科雷 当他说您应该以更多的“业务友好”措辞制定情况时。但是,使用SpecFlow来驱动您的域模型的实现,捕获异常并在其上做主张可能会派上用场。
顺便说一句:在Tekpub上查看Rob Conery的屏幕截图,以获取有关使用SpecFlow的一些非常好的技巧: http://tekpub.com/view/concepts/5
可以在功能级别行为或/和单位级别行为上实践BDD。
SpecFlow是BDD工具,专注于特征级别行为。异常不是您应该在功能级别行为上指定/观察的东西。应在单位级行为上指定/观察例外。
将SpecFlow方案视为非技术利益相关者的实时规范。您也不会在规格中写出异常,而是在这种情况下系统的行为。
如果您没有任何非技术利益相关者,那么SpecFlow是您错误的工具!如果没有人有兴趣阅读它们,请不要浪费能量创建业务可读规格!
有一些BDD工具专注于单位级别行为。在.NET中最受欢迎的是MSPEC(http://github.com/machine/machine.specifications)。单位级别的BDD也很容易成为具有标准单位测试框架的实践。
就是说,你 仍然可以检查SpecFlow中的例外.
以下是有关单位级与BDD的更多讨论,有关功能级别的BDD:SpecFlow/BDD与单元测试 BDD用于接受测试与单位测试的BDD(OR:ATDD vs. TDD)
还要查看此博客文章:分类BDD工具(单位测试驱动与接受测试驱动)和一些BDD历史记录
更改场景不例外是使方案更加面向用户的好方法。但是,如果您仍然需要工作,请考虑以下内容:
捕获异常(我真的建议您在调用操作并将其传递到方案上下文的步骤中捕获特定的例外。
[When("I press add")] public void WhenIPressAdd() { try { _calc.Add(); } catch (Exception err) { ScenarioContext.Current[("Error")] = err; } }
验证该异常存储在方案上下文中
[Then(@"it should throw an exception")] public void ThenItShouldThrowAnException() { Assert.IsTrue(ScenarioContext.Current.ContainsKey("Error")); }
PS非常接近现有答案之一。但是,如果您尝试使用语法从ScenarioContext获得值,则如下:
var err = ScenarioContext.Current["Error"]
如果“错误”密钥不存在(这将使所有使用正确参数执行计算的方案失败),将会引发另一个异常。所以 ScenarioContext.Current.ContainsKey
可能更合适
如果您正在测试用户交互,我只会建议专注于用户体验已经说过什么:“然后向用户提供了错误消息”。但是,如果您要测试UI以下的水平,我想分享我的经验:
我正在使用SpecFlow来开发业务层。就我而言,我不在乎UI的交互,但是我仍然发现BDD方法和规格非常有用。
在业务层中,我不想说“然后向用户呈现错误消息”的规格,但实际上验证了该服务是否正确响应错误的输入。我已经做了一段时间,已经说过在“何时”中捕获例外并在“然后”验证它的情况,但是我发现此选项不是最佳的,因为如果您重复使用“何时”步骤,则可以吞咽您没想到的例外。
目前,我正在使用显式“然后”条款,有时没有“何时”,以下方式:
Scenario: Adding with an empty stack causes an error
Given I have entered nothing into the calculator
Then adding causes an error X
这使我能够一步一步专门编写动作和异常检测。我可以将其重复使用以测试我想要的多数错误情况,并且不会使我在“何时”步骤中添加无关的代码。
我的解决方案涉及几个要实现的项目,但最终它看起来会更加优雅:
@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 中的错误 什么时候 步骤,因此您仍然可能无法通过测试 然后 验证异常。必须通过反思来做到这一点 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);
}