在阅读了一篇关于单元测试行为而不是状态的有趣文章后,我开始意识到我的单元测试通常与我的代码紧密耦合,因为我正在使用模拟。
我无法在没有模拟的情况下编写单元测试图像,但事实是这些模拟将我的单元测试与我的代码非常相似,因为期望和返回调用。
例如,当我创建一个使用模拟的测试时,我会记录对特定模拟的所有调用并分配返回值。
现在,当我因任何原因改变实际代码的实现时,很多测试都会中断,因为模拟没有预期调用,也迫使我更新单元测试,并且有效地强迫我实现每次更改两次......
这种情况发生了很多。
这个问题是否是使用模拟所固有的问题,我应该学会忍受它,还是我做了一些根本错误的事情?
请赐教:)
当然,欢迎随附解释的明显例子。
当我创建一个使用模拟的测试时,
我记录了所有特定的电话
模拟并分配返回值
听起来你可能过度指定期望。
尝试在测试中尽可能少地设置代码:存根(而不是期望)与当前测试无关的所有行为,并且仅指定使测试工作绝对需要的返回值。
这个答案 包括一个简明的例子(以及另一个更详细的解释)。
我的经验是只在(子)系统的bounderies上使用模拟。如果我有两个强相关的类,我不会嘲笑他们appart但是一起测试它们。一个例子可能是复合和访客。如果我测试一个具体的访问者,我不会使用模拟复合而是创建真正的复合材料。有人可能会说这不是单元测试(取决于什么是单位的定义)。但那并不重要。我试图实现的是:
- 写可读测试(没有模拟的测试大多数时候更容易阅读)。
- 仅测试焦点的代码区域(在示例中为concreate visitor和组合的相关部分)。
- 编写快速测试(只要我只实例化几个类,在示例中具体复合,这不是一个问题......注意传递创作)。
只有当我遇到子系统的边界时,我才会使用模拟器。示例:我有一个可以将自身渲染到渲染器的复合,如果我测试复合的渲染逻辑,我会模拟渲染器。
测试行为而不是状态看起来一开始就提出,但总的来说,我会测试状态,因为最终的测试很容易维护。模拟是一门大炮。不要用大锤敲打坚果。
如果由于它们中断而修复测试,则不按预期使用它们。
如果方法的行为发生更改,则在测试驱动开发中,您首先要更改测试以期望新行为,然后实现新行为。
这里有几个很好的答案,但对我来说,一个好的经验法则是测试 要求 方法,而不是实现。有时这可能意味着使用模拟对象,因为交互是需求,但通常最好不要测试方法的返回值或对象状态的变化。