我使用NMock2,并且我已经起草了以下NMock类来表示一些常见的模拟框架概念:
Expect
:这指定了一个模拟方法应返回的内容,并说调用必须发生或测试失败(伴随调用VerifyAllExpectationsHaveBeenMet()
)。
Stub
:这指定了模拟方法应返回的内容,但不能导致测试失败。
那我该怎么做?
答案 0 :(得分:17)
很多模拟框架都带来了模拟和模拟的概念。存根更接近&更接近于他们可以被认为在功能上几乎相同的点。然而,从概念的角度来看,我通常会尝试遵循这个惯例:
当您确保每个单元测试只测试一件事时,这会变得更加清晰。当然,如果您尝试在一次测试中测试所有内容,那么您也可以期待一切。但是,只考虑特定单元测试所检查的内容,您的代码更清晰,因为您可以一目了然地看到测试的目的是什么。
这样做的另一个好处是,你可以稍微改变一下,而不是变化。当更改导致中断时,获得更好的错误消息。换句话说,如果你微妙地改变你的实现的某些部分,你更有可能只打破一个测试用例,这将显示你到底发生了什么,而不是一整套测试打破&只是制造噪音。
编辑:基于一个人为的例子可能会更清楚,其中计算器对象审核数据库的所有添加内容(伪代码)......
public void CalculateShouldAddTwoNumbersCorrectly() {
var auditDB = //Get mock object of Audit DB
//Stub out the audit functionality...
var calculator = new Calculator(auditDB);
int result = calculator.Add(1, 2);
//assert that result is 3
}
public void CalculateShouldAuditAddsToTheDatabase() {
var auditDB = //Get mock object of Audit DB
//Expect the audit functionality...
var calculator = new Calculator(auditDB);
int result = calculator.Add(1, 2);
//verify that the audit was performed.
}
因此,在第一个测试案例中,我们正在测试Add
方法的功能。不关心审计事件是否发生,但是我们碰巧知道计算器不会使用auditDB引用,因此我们只是将它存根,以便为我们提供最少的功能来使我们的特定测试用例工作。在第二个测试中,我们专门测试当你执行Add
时,审计事件发生了,所以我们在这里使用期望(注意我们甚至不关心结果是什么,因为那不是我们'重新测试)。
是的,您可以将这两种情况合二为一,并且做预期并断言你的结果是3,但是你在一个单元测试中测试了两个案例。这会使你的测试更加脆弱(因为有更大的表面区域可以改变以打破测试)并且不太清楚(因为当合并的测试失败时,它不是立即明显的问题是什么...是添加不起作用,或审核无效?)
答案 1 :(得分:4)
“期待操作,存根查询”。如果调用应该改变被测对象之外的世界状态,那么可以期待它 - 你关心它是如何被调用的。如果它只是一个查询,你可以调用它一次或六次而不改变系统状态,然后存根调用。
还有一件事,请注意区别在于存根和期望,即个别调用,不一定是整个对象。
答案 2 :(得分:1)
嗯......恕我直言,这不可能更简单:如果您的测试是关于确保您的Presenter会调用Save,请执行Expect。如果您的测试是关于确保您的Presenter将优雅地处理异常,如果Save抛出,请执行存根。
有关详细信息,请查看this podcast by Hanselman and Osherove(单元测试艺术作者)