前段时间我读了Martin Fowler撰写的Mocks Aren't Stubs文章,我必须承认我有点害怕外部依赖关于增加的复杂性所以我想问:
单元测试时使用的最佳方法是什么?
最好总是使用模拟框架自动模拟被测试方法的依赖关系,还是更喜欢使用更简单的机制,例如测试存根?
答案 0 :(得分:11)
正如咒语所说'去做最简单的事情,可能有用。'
避免使用模拟总是,因为它们会使测试变得脆弱。您的测试现在对实现调用的方法有复杂的了解,如果模拟的接口或您的实现发生了变化......您的测试会中断。这很糟糕因为你会花费额外的时间让你的测试运行而不是让你的SUT运行。 测试不应与实施不合适。
因此,请使用您最好的判断..我更喜欢嘲笑它会帮助我保存写作 - 使用n>> 3方法更新假类。
更新结语/审议:
(感谢Toran Billups举行的一次模拟测试。见下文)
嗨,道格,我想我们已经超越了另一场圣战 - 经典TDDers与Mockist TDDers。我想我属于前者。
答案 1 :(得分:8)
由于期望,我通常更喜欢使用模拟。当您在返回值的存根上调用方法时,它通常只会返回一个值。但是当你在模拟上调用一个方法时,它不仅会返回一个值,而且还强制要求你设置该方法首先被调用。换句话说,如果设置期望然后不调用该方法,则会抛出异常。当你设定一个期望时,你基本上是在说“如果这个方法没有被调用,那就出错了。”反之亦然,如果你在模拟上调用一个方法并且没有设置期望,它将抛出一个异常,实质上是说“嘿,当你没想到它时,你正在做什么调用这个方法。”
有时你不希望对你所调用的每个方法都有期望,所以一些模拟框架会允许像模拟/存根混合一样的“部分”模拟,因为只有你设定的期望被强制执行,而且每个其他方法都是如此call更像是一个stub,因为它只返回一个值。
使用存根的一个有效位置我可以想到,但是,当您将测试引入遗留代码时。有时通过子类化您正在测试的类而不是重构所有内容以使模拟变得容易甚至可能来创建存根更容易。
对此......
避免使用模拟,因为它们会使测试变得脆弱。如果模拟的接口发生变化,您的测试现在对实现调用的方法有了复杂的了解......您的测试会中断。因此,请使用您的最佳判断。<
...我说如果我的界面发生变化,我的测试最好休息一下。因为单元测试的重点在于它们准确地测试我现在存在的代码。
答案 2 :(得分:2)
这取决于您正在进行的测试类型。如果您正在进行基于行为的测试,则可能需要动态模拟,以便验证是否发生了与您的依赖性的某些交互。但是,如果您正在进行基于状态的测试,则可能需要存根,以便验证值/ etc
例如,在下面的测试中,您注意到我存根视图,因此我可以验证属性值是否已设置(基于状态的测试)。然后我创建了一个服务类的动态模拟,这样我就可以确保在测试期间调用一个特定的方法(基于交互/行为的测试)。
<TestMethod()> _
Public Sub Should_Populate_Products_List_OnViewLoad_When_PostBack_Is_False()
mMockery = New MockRepository()
mView = DirectCast(mMockery.Stub(Of IProductView)(), IProductView)
mProductService = DirectCast(mMockery.DynamicMock(Of IProductService)(), IProductService)
mPresenter = New ProductPresenter(mView, mProductService)
Dim ProductList As New List(Of Product)()
ProductList.Add(New Product())
Using mMockery.Record()
SetupResult.For(mView.PageIsPostBack).Return(False)
Expect.Call(mProductService.GetProducts()).Return(ProductList).Repeat.Once()
End Using
Using mMockery.Playback()
mPresenter.OnViewLoad()
End Using
'Verify that we hit the service dependency during the method when postback is false
Assert.AreEqual(1, mView.Products.Count)
mMockery.VerifyAll()
End Sub
答案 3 :(得分:2)
最好使用组合,你必须使用自己的判断。这是我使用的指南:
第二种模拟是一种必要的邪恶。真正发生的事情是,无论你使用存根还是模拟,在某些情况下,你必须比你想要的更多地耦合你的代码。当发生这种情况时,最好只使用模拟而不是存根,因为您将知道何时该耦合中断并且您的代码不再按照您的测试认为的方式编写。当你这样做时,最好在测试中留下评论,以便无论谁打破它都知道他们的代码没有错,测试就是。
同样,这是代码气味和最后的手段。如果您发现需要经常这样做,请尝试重新考虑编写测试的方式。
答案 4 :(得分:2)
不要介意统计与互动。想想角色和关系。如果一个对象与一个邻居协作以完成其工作,那么该关系(如在界面中表示的)是使用模拟进行测试的候选者。如果对象是具有一些行为的简单值对象,则直接对其进行测试。我无法看到手工编写模拟(甚至是存根)的重点。这就是我们所有人开始和重构的方式。
如需更长时间的讨论,请考虑查看http://www.mockobjects.com/book
答案 5 :(得分:1)
在this blog post中阅读Luke Kanies关于这个问题的讨论。他引用a post from Jay Fields甚至暗示使用[相当于ruby's / mocha的] stub_everything更适合使测试更加健壮。引用Fields的最后一句话:“Mocha使定义模拟和定义存根一样容易,但这并不意味着你应该总是喜欢模拟。实际上,我通常更喜欢存根并在必要时使用模拟。 “