前段时间我读过Martin Fowler撰写的Mocks Are Not Stubs文章,我必须承认,我有点害怕外部依赖关于增加的复杂性,所以我想问:
单元测试时使用的最佳方法是什么?
是不是总是使用模拟框架来自动模拟被测试方法的依赖关系,还是更喜欢使用更简单的机制,例如测试存根?
正如咒语所说的那样"去做最简单的事情."
如果假类可以完成工作,那就去做吧.
如果您需要一个具有多个要模拟的方法的接口,请使用模拟框架.
避免使用嘲笑总是因为它们使测试脆.您的测试现在对实现调用的方法有复杂的了解,如果模拟的接口或您的实现发生了变化......您的测试会中断.这很糟糕因为你会花费额外的时间让你的测试运行而不是让你的SUT运行.测试不应与实施不适当地密切相关.
所以用你最好的判断..我更喜欢嘲笑它会帮助我节省写作 - 用n >> 3方法更新假类.
更新结语/审议:(
感谢Toran Billups举例说明了一个模拟测试.见下文)
嗨道格,我认为我们已经超越了另一场圣战 - 经典TDDers vs Mockist TDDers.我想我属于前者.
如果我正在测试#101 Test_ExportProductList,我发现我需要向IProductService.GetProducts()添加一个新的参数.我做到了这个测试绿色.我使用重构工具来更新所有其他引用.现在我发现所有称这个成员的模拟测试现在都爆炸了.然后我必须回去更新所有这些测试 - 浪费时间.为什么ShouldPopulateProductsListOnViewLoadWhenPostBackIsFalse失败?是因为代码坏了吗?相反,测试被打破了.我赞成一个测试失败= 1个地方来修复.嘲弄频率与此相反.存根会更好吗?如果它我有一个fake_class.GetProducts()..确定一个地方改变而不是猎枪手术多个Expect电话.最后这是一个风格的问题..如果你有一个常用的实用方法MockHelper.SetupExpectForGetProducts() - 这也足够..但你会发现这是不常见的.
如果在测试名称上放置白色条带,则难以阅读测试.很多用于模拟框架的管道代码隐藏了正在执行的实际测试.
要求你学习这种嘲弄框架的特殊风格
由于期望,我通常更喜欢使用模拟.当您在返回值的存根上调用方法时,它通常只会返回一个值.但是当你在模拟上调用一个方法时,它不仅会返回一个值,而且还强制要求你设置该方法首先被调用.换句话说,如果设置期望然后不调用该方法,则会抛出异常.当你设定一个期望时,你基本上是在说"如果这个方法没有被调用,那就出错了." 反之亦然,如果你在模拟上调用一个方法并且没有设置期望,它将抛出一个异常,实质上是说"嘿,当你没想到它时,你正在做什么调用这个方法."
有时你不希望对你所调用的每个方法都有期望,所以一些模拟框架将允许像模拟/存根混合一样的"部分"模拟,因为只有你设置的期望被强制执行,并且所有其他方法调用都被处理更像是一个存根,因为它只返回一个值.
但是,一个有效的地方可以使用我可以想到的存根,当你将测试引入遗留代码时.有时通过子类化您正在测试的类而不是重构所有内容以使模拟变得容易甚至可能来制作存根更容易.
对此......
避免使用模拟,因为它们会使测试变得脆弱.如果模拟的接口发生变化,您的测试现在对实现调用的方法有了复杂的了解......您的测试会中断.所以用你最好的判断.. <
...我说如果我的界面发生变化,我的测试最好休息一下.因为单元测试的重点在于它们准确地测试我现在存在的代码.