如果我使用注入到 SUT 中的对象的模拟模拟作为参数,那么在重构期间重新组织代码以调用同一模拟的另一个非模拟方法会发生什么?我的测试会失败,我必须返回并更改我的测试并为这个新调用设置它们(与重构代码时我想要做的相反)
如果这是重构过程中常见的情况,那么除了模拟外部资源密集型实体(网络、数据库等)之外,使用模拟有何用处?
我正在使用模拟来模拟需要几个小时才能设置的对象,因为我的团队似乎非常喜欢深度聚合对象。
你是对的,重构可能会破坏依赖于模拟的代码。 Mockito 不知道方法
foo(int start, int end)
和 foo(int start)
可以完成同样的事情,如果你在重构时在它们之间切换,你的 Mockito 模拟很可能会崩溃。 Mockito 确实为未存根的调用提供了合理的默认值,例如 0、null 或空列表;然而,许多重构将需要更现实的值。
一般来说,我听说过 当系统运行正常时,测试或测试夹具会失败,这被称为“脆弱性”。
部分原因是模拟框架的选择:Mockito 最初是 EasyMock 的一个分支,如果调用太多或太少,EasyMock 默认会失败,但 Mockito 会忽略意外调用,否则提供“nice” “默认行为。另一部分取决于您如何使用框架,其中验证不必要的细节(不重要的调用或参数)可能会使模拟比应有的更加脆弱。
Mockito 擅长嘲笑的事物:
Mockito 不适合嘲笑:
final
选择之类的实现细节开始潜入。这可能是创建您可以控制的包装器的一个很好的理由。值得一提的是,没有测试替身是绝对安全的;您的系统可能会缓存、组合、延迟、修改或以其他方式调整与其协作者的交互,所有这些都可能会破坏您编写的任何测试替身。编写灵活测试的艺术是依赖尽可能少的实现细节,平衡脆弱性风险与彻底测试系统及其与外界交互的要求。
综上所述,要直接回答“使用模拟如何有任何用处”的问题,请参阅 JB Nizet 的伟大的类比:如果您正在尝试创建一个炸弹雷管,您可能想测试它,但是使用真品的成本实在是太大了。所涉及的难度(以及测试替身的最佳选择)将根据所讨论的炸弹是否有一百个小触发器或单个
boom()
方法而有所不同。
有关测试替身(包括模拟、假货和虚拟对象的类别)及其优缺点的更多详细信息,请参阅 Martin Fowler 的文章 “模拟不是存根”。
我建议你只模拟所需的最低限度的内容。 Mockito 有很多工具可以做到这一点(间谍、以某种方式调用方法时返回特定数据/模拟的能力等),但最终归结为在代码中具有可测试的“接缝”。如果您还没有阅读过,我建议您阅读 Michael Feather 的书 有效处理旧代码,获取有关如何执行此操作的许多建议。