【问题标题】:Effects of Mockito Mocks on Unit Tests When Refactoring重构时 Mockito 模拟对单元测试的影响
【发布时间】:2015-09-30 19:13:54
【问题描述】:
如果我使用被注入 SUT 的对象的 mockito 模拟作为参数,如果在重构期间重新组织代码以调用同一模拟的另一个非模拟方法会发生什么?我的测试将失败,我必须返回并更改我的测试并为这个新调用设置它们(与重构代码时我想要做的相反)
如果这在重构过程中很常见,那么除了模拟外部资源密集型实体(网络、数据库等)之外,使用模拟还有什么用处?
鉴于我的团队似乎喜欢极其深的聚合对象,我正在使用模拟来模拟需要数小时才能设置的对象。
谢谢!
【问题讨论】:
标签:
java
refactoring
tdd
mockito
【解决方案1】:
你说得对,重构可能会破坏依赖于模拟的代码。 Mockito 不知道 foo(int start, int end) 和 foo(int start) 方法完成相同的事情,如果你在重构时在它们之间切换,你的 Mockito 模拟很可能会中断。 Mockito 确实为未存根的调用提供了合理的默认值,例如 0、null 或空列表;但是,许多重构需要更现实的值。
一般来说,我听说当系统正常运行时,测试或测试夹具会失败的趋势将其称为“脆弱性”。
这部分源于对模拟框架的选择:Mockito 最初是作为 EasyMock 的一个分支,如果调用太多或太少,EasyMock 默认会失败,但 Mockito 会忽略意外调用,否则会提供“不错”的默认行为。另一部分取决于您如何使用框架,其中验证不必要的细节(不重要的调用或参数)可能会使模拟变得比它们必须的更脆弱。
Things Mockito 擅长模拟:
- 外部资源密集型依赖项(或其包装)。
- 接口。这里几乎不会出错。
- 小 API 表面。如果您的 API 表面有一种或两种方法,则不太可能捕捉到您所描述的情况。
- 尚不存在的协作者,即使他们有较大的 API 表面。临时脆性测试可以稍后修复。
Things Mockito 不适合模拟:
- 非常大的 API 表面和 DSL。想象一下使用 Mockito 实现 Builder 模式!更喜欢使用真实对象,或者编写内存中的 fake 或其他测试替身。
- 有状态的对象,例如数据库和模型对象。如果真品不行,假货绝对是更好的选择。
- 您无法控制的具体类。此时,
final 的选择等实现细节开始潜入。这可能是创建您可以控制的包装器的一个很好的理由。
- 具有现有且经过良好测试的实现的任何内容。既然有真正的实现,为什么还要经历所有这些麻烦并失去测试保真度?
值得一提的是,没有测试替身是绝对安全的;您的系统可能会缓存、组合、延迟、修改或以其他方式调整与其协作者的交互,所有这些都可能破坏您编写的几乎任何测试替身。编写灵活测试的艺术在于尽可能少地依赖实现细节,平衡脆弱性风险与彻底测试系统及其与外界交互的要求。
综上所述,要直接回答“使用模拟有什么用”的问题,请参阅 JB Nizet 的 great analogy here:如果您正在尝试制造炸弹雷管,您可能想对其进行测试,但是使用真品的成本实在是太大了。所涉及的难度(以及测试替身的最佳选择)将根据所讨论的炸弹是否具有一百个小触发器或单个 boom() 方法而有所不同。
有关测试替身(包括模拟、伪造和虚拟对象的类别)及其优缺点的更多详细信息,请参阅 Martin Fowler 的文章"Mocks aren't Stubs"。