【问题标题】:Mocking ScheduledExecutorService vs the "Don't mock type you don't own" philosophy模拟 ScheduledExecutorService 与“不要模拟你不拥有的类型”的哲学
【发布时间】:2017-11-22 02:04:44
【问题描述】:

Mocking ScheduledExecutorService 确实会使测试我的类更容易,但根据 mockito recommendations 这似乎是个坏主意,因为模拟类的逻辑可能会以一种不正确的方式使用而改变,但是单元测试仍然会报告成功。

似乎为它编写一个包装器将是一种“干净”的方式,但我有一种感觉,这只会导致接口的完全重复,这只会让我的代码不那么简单。我想遵循this answer 的实用建议,但我不确定ScheduledExecutorService 的合同是否会一直保持不变。

我可以假设ScheduledExecutorService(或更一般地说,JRE 库中的任何其他类)的现有方法的合同永远不会改变吗?如果没有,如果我在集成测试中测试它的正确使用,同时在单元测试中直接模拟它就足够了吗?

【问题讨论】:

  • ScheduledExecutorService 是一个接口 - 所以它就是一个契约,你可以假设它在 JDK 中的各种实现都遵循契约。
  • 谢谢!但是我可以确定后续Java版本中的接口会指定相同的合同吗?仅出于示例的目的,假设将添加一个新方法,该方法应在任何调度调用之前调用。然后我的单元测试不会通知我这个变化,但整个软件将无法正常工作。还是我应该始终坚持使用一个版本的 JDK/JRE 并在升级时彻底检查所有内容?
  • 较新的 JDK 版本大多向后兼容,并且在发行说明中清楚地指出了重大更改(通常是错误修复)。如果您对此感到不舒服,您可以进行 1 个测试来检查 SES 实现是否按预期(单元或集成)工作并完成。

标签: java unit-testing mocking mockito


【解决方案1】:

与其说是规则,不如说是指导方针;做最有可能导致干净、可靠和不易碎的测试的事情。如您引用的文件中所述:

这不是一条硬线,但越过这条线可能会产生影响! (很可能会)

这里有一件重要的事情是,“不要模拟你不拥有的类型”通常是指 concreteinternal 类型,因为它们更有可能在版本之间改变它们的行为,或者获得或失去像 finalstatic 这样 Mockito 的动态覆盖可能无法接受的修饰符。毕竟,如果您要手动子类化第三方类,Java 会抛出编译器错误; Mockito 的语法会在测试运行时对您隐藏它。

列出我想到的因素:

  • 正如 assylias 在 cmets 中指出的那样,您指的是 Java 接口,它使您免受对 final 方法或方法可见性的常见更改。
  • 该接口文档齐全,专为第三方扩展而设计,这也是 Java 不太可能对接口的一般约定进行重大更改的另一个原因。
  • 有问题的接口是 Java 中一个非常常用的接口,总体上有很多用户,并且存在很多向后兼容性问题。与较小的库或正在积极开发的库相比,您不太可能受到重大更改的影响。甚至有人可能会说,JRE 与 Java 语言处于这样的锁定步骤,​​您需要担心破坏 syntax 更改而不是破坏 interface 更改。李>

虽然我坚信“不要模拟你不拥有的类型”作为一般的启发式或代码味道,但我在这里同意你的观点,即该类型值得模拟,并且——除非你要编写并测试一个完整的实现以用于其他测试——这是您前进的最佳途径。

【讨论】:

  • 这就是我争论的部分:“(它很可能会)”。至于我的经验,相反的可能性更大:如果您不模拟 您不拥有的类型...,您的测试会变得脆弱
  • @Timothy 好的,但根据我的经验,我同意 Mockito 文档:如果你模拟你不拥有的类型,测试会变得脆弱...... 如果它们不是设计的接口用于外部子类化。更糟糕的是,这些破坏往往以非常不透明的方式发生,因为 Mockito 无法警告尝试模拟 final 方法或类。
  • “Mockito 无法警告尝试模拟最终方法或类。” 所以这就是问题所在:依赖方法或类变为 final 并带有更新的版本?一次:上次我尝试模拟 final 方法或类 Mockito 确实 抱怨正是这一点。第二:你上次在现实生活中发生这种情况是什么时候,依赖方法(你使用的代码)更改为final?对我来说:永远...
  • @Timothy (1) Mockito 的文档指定它无法检测到 final 方法,至少对于 1.x:"Mockito cannot warn you about mocking final methods so be vigilant."。 (2) 您从事的软件项目越大,您就越有可能参考正在开发的内部库。 :) 但是,可见性/静态/最终更改加入了合同更改列表的其余部分,这些更改将破坏基于 Cavalier Mockito 的测试。
  • (1) "对于 1.x 至少:“Mockito 无法警告您模拟最终方法” 我尝试了1.9.5(5 岁)和@ 987654331@(三岁)并得到:Mockito 无法模拟/监视以下内容:-最终类-匿名类-原始类型也许这里的文档不是最新的......
【解决方案2】:

我会说 “不要模拟你不拥有的类型!” 是正确推理得出的错误结论。

只有在您的 API 发生更改或您的代码使用的依赖项的API 的一部分 时,才需要更改单元测试。。 p> 例子:

您使用依赖项的接口作为输入参数,但您测试的代码仅使用该接口中的一种方法。如果您不模拟此接口(这是一个您不拥有的类型),您必须创建自己的虚拟实现来实现所有接口方法,即使是您不使用的方法。

如果您更改该依赖项的版本,此接口可能有其他方法和/或某些方法已被删除。您必须在整个程序中更改所有您的此接口的实现。如果您模拟了此界面,则无需更改测试,它们仍然让您确信您的代码行为在所需的重构之后没有改变。

此外,您的单元测试应该只会因为 您的代码 的行为发生变化而失败,而不是因为依赖行为的变化。 依赖关系行为的更改应使用您为依赖关系行为设置的单独单元测试(如果它对您的应用程序至关重要)和/或集成测试

【讨论】:

  • 我可以将其总结为“为您模拟的任何内容编写单元测试,无论是您自己的还是其他人的课程”?换句话说,这是否意味着我应该为我使用的外部类(在我的例子中,ScheduledThreadPoolExecutor)编写一个单元测试,而不管他们在开发他们的项目中也可能有单元测试?这很有趣,因为在这种情况下,如果我对外部类的单元测试失败,这意味着我将不得不修改我自己的类的行为,这些类的单元测试运行成功。
  • @AlbertP “我可以将其总结为“为你模拟的任何东西编写单元测试,无论是你自己的还是其他人的课程”?” 不,只有当你有疑问时您所依赖的行为可能会以影响应用程序正确性的方式发生变化。至于我的经验,通常情况并非如此。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-05-18
  • 2019-04-12
  • 2022-01-08
  • 2012-03-02
  • 1970-01-01
相关资源
最近更新 更多