129

我正在研究为我的项目使用哪个模拟框架,并将其范围缩小到JMockitMockito

我注意到Mockito在 Stackoverflow上被评为“ Java 的最佳模拟框架”。
在比较JMockit的“模拟工具比较矩阵”的功能时,似乎JMockit具有多个不同的功能。

有没有人有任何关于Mockito可以做什么而JMockit无法实现的具体信息(不是意见),反之亦然?

4

5 回答 5

142

2019 年 9 月更新:Spring Boot 支持(默认)的唯一模拟框架是Mockito。如果你使用 Spring,答案就很明显了。


我想说的是JMockitPowerMock之间的竞争,然后是Mockito

我会留下“普通”的 jMock 和 EasyMock,因为它们只使用代理和 CGLIB,而不像较新的框架那样使用 Java 5 工具。

jMock 也有超过 4 年没有稳定的版本。jMock 2.6.0 从 RC1 到 RC2 需要 2 年,然后在它真正发布之前又需要 2 年。

关于代理和 CGLIB 与检测:

(EasyMock 和 jMock)基于 java.lang.reflect.Proxy,需要实现接口。此外,它们还支持通过 CGLIB 子类生成为类创建模拟对象。因此,所述类不能是最终的,只能模拟可覆盖的实例方法。然而,最重要的是,当使用这些工具时,被测代码的依赖关系(即,给定被测类所依赖的其他类的对象)必须由测试控制,以便可以将模拟实例传递给客户端这些依赖项。因此,不能简单地在我们要为其编写单元测试的客户端类中使用 new 运算符来实例化依赖项。

最终,传统模拟工具的技术限制对生产代码施加了以下设计限制:

  1. 每个可能需要在测试中模拟的类必须要么实现单独的接口,要么不是最终的。
  2. 要测试的每个类的依赖关系必须要么通过可配置的实例创建方法(工厂或服务定位器)获得,要么暴露用于依赖注入。否则,单元测试将无法将依赖项的模拟实现传递给被测单元。
  3. 由于只能模拟实例方法,因此要进行单元测试的类不能在其依赖项上调用任何静态方法,也不能使用任何构造函数实例化它们。

以上是从http://jmockit.org/about.html复制的。此外,它以多种方式在自身 (JMockit)、PowerMock 和 Mockito 之间进行比较:

现在还有其他用于 Java 的模拟工具也克服了传统工具的限制,它们之间有 PowerMock、jEasyTest 和 MockInject。最接近 JMockit 的特性集的是 PowerMock,所以在这里我将简要评估一下(另外,其他两个比较有限,似乎不再积极开发了)。

JMockit 与 PowerMock

  • 首先,PowerMock 没有提供完整的模拟 API,而是作为另一个工具的扩展,目前可以是 EasyMock 或 Mockito。对于这些工具的现有用户来说,这显然是一个优势。
  • 另一方面,JMockit 提供了全新的 API,尽管它的主要 API(期望)与 EasyMock 和 jMock 相似。虽然这会产生更长的学习曲线,但它也允许 JMockit 提供更简单、更一致且更易于使用的 API。
  • 与 JMockit Expectations API 相比,PowerMock API 更“低级”,迫使用户弄清楚并指定需要为测试准备哪些类(使用 @PrepareForTest({ClassA.class, ...}) 注解) 并需要特定的 API 调用来处理生产代码中可能存在的各种语言构造:静态方法 (mockStatic(ClassA.class))、构造函数 (suppress(constructor(ClassXyz.class)))、构造函数调用 ( expectNew(AClass.class))、部分模拟 (createPartialMock(ClassX.class, "methodToMock")) 等。
  • 使用 JMockit 期望,各种方法和构造函数都以纯声明的方式进行模拟,通过 @Mocked 注释中的正则表达式指定部分模拟,或者通过简单地“取消模拟”没有记录期望的成员;也就是说,开发人员只需为测试类声明一些共享的“模拟字段”,或者为单个测试方法声明一些“本地模拟字段”和/或“模拟参数”(在最后一种情况下,@Mocked 注释通常不会被需要)。
  • JMockit 中可用的某些功能,例如对模拟 equals 和 hashCode 的支持、重写方法等,目前在 PowerMock 中不受支持。此外,没有与 JMockit 在测试执行时捕获指定基本类型的实例和模拟实现的能力等效,而测试代码本身不了解实际的实现类。
  • PowerMock 使用自定义类加载器(通常每个测试类一个)来生成模拟类的修改版本。如此大量使用自定义类加载器可能会导致与第三方库的冲突,因此有时需要在测试类上使用 @PowerMockIgnore("package.to.be.ignored") 注释。
  • JMockit 使用的机制(通过“Java 代理”进行运行时检测)更简单、更安全,尽管在 JDK 1.5 上开发时确实需要将“-javaagent”参数传递给 JVM;在 JDK 1.6+(它始终可以用于开发,即使在旧版本上部署)没有这样的要求,因为 JMockit 可以通过使用 Attach API 透明地按需加载 Java 代理。

另一个最近的模拟工具是 Mockito。尽管它并没有试图克服旧工具(jMock、EasyMock)的限制,但它确实引入了一种新的模拟行为测试风格。JMockit 还通过 Verifications API 支持这种替代样式。

JMockit vs Mockito

  • Mockito 依赖于对其 API 的显式调用,以便在记录 (when(...)) 和验证 (verify(...)) 阶段之间分离代码。这意味着在测试代码中对模拟对象的任何调用也需要调用模拟 API。此外,这通常会导致重复 when(...) 和 verify(mock)... 调用。
  • 使用 JMockit,不存在类似的调用。当然,我们有 new NonStrictExpectations() 和 new Verifications() 构造函数调用,但它们每次测试只发生一次(通常),并且与对模拟方法和构造函数的调用完全分开。
  • Mockito API 在用于调用模拟方法的语法中包含几个不一致的地方。在记录阶段,我们有类似 when(mock.mockedMethod(args))... 的调用,而在验证阶段,同样的调用将被写为 verify(mock).mockedMethod(args)。请注意,在第一种情况下,对 mockedMethod 的调用是直接在模拟对象上进行的,而在第二种情况下,它是在 verify(mock) 返回的对象上进行的。
  • JMockit 没有这种不一致,因为对模拟方法的调用总是直接在模拟实例本身上进行的。(只有一个例外:为了匹配同一个模拟实例上的调用,使用 onInstance(mock) 调用,从而产生类似 onInstance(mock).mockedMethod(args) 的代码;不过,大多数测试不需要使用它。 )
  • 就像其他依赖方法链接/包装的模拟工具一样,Mockito 在存根 void 方法时也会遇到不一致的语法。例如,你写 when(mockedList.get(1)).thenThrow(new RuntimeException()); 对于非 void 方法,以及 doThrow(new RuntimeException()).when(mockedList).clear(); 对于一个无效的。使用 JMockit,它始终是相同的语法:mockedList.clear(); 结果 = 新的运行时异常();。
  • 在使用 Mockito 间谍时出现了另一个不一致:允许在间谍实例上执行真实方法的“模拟”。例如,如果 spy 引用一个空列表,那么您需要编写 doReturn("foo").when(spy).get( 0)。使用 JMockit,动态模拟功能提供了与间谍类似的功能,但没有这个问题,因为真正的方法只在重放阶段执行。
  • 在 EasyMock 和 jMock(Java 的第一个模拟 API)中,重点完全是记录模拟方法的预期调用,用于(默认情况下)不允许意外调用的模拟对象。这些 API 还为允许意外调用的模拟对象提供了允许调用的记录,但这被视为第二类功能。此外,使用这些工具,在执行测试代码后,无法显式验证对模拟的调用。所有此类验证都是隐式自动执行的。
  • 在 Mockito(以及 Unitils Mock)中,采取了相反的观点。在测试期间可能发生的所有对模拟对象的调用,无论是否记录,都是允许的,这是绝对不允许的。验证是在被测代码执行后显式执行的,而不是自动执行的。
  • 这两种方法都过于极端,因此都不是最优的。JMockit Expectations & Verifications 是唯一允许开发人员为每个测试无缝选择严格(默认预期)和非严格(默认允许)模拟调用的最佳组合的 API。
  • 更清楚地说,Mockito API 有以下缺点。如果您需要验证在测试期间是否发生了对非 void 模拟方法的调用,但测试需要该方法的返回值与返回类型的默认值不同,则 Mockito 测试将具有重复代码:在记录阶段调用 when(mock.someMethod()).thenReturn(xyz),在验证阶段调用 verify(mock).someMethod()。使用 JMockit,始终可以记录严格的期望,而无需明确验证。或者,可以为任何记录的非严格期望指定调用计数约束(次数 = 1)(对于 Mockito,此类约束只能在 verify(mock, constraint) 调用中指定)。
  • Mockito 在顺序验证和完整验证(即检查对模拟对象的所有调用是否都经过显式验证)方面的语法很差。在第一种情况下,需要创建一个额外的对象,并对其进行验证调用:InOrder inOrder = inOrder(mock1, mock2, ...)。在第二种情况下,需要调用 verifyNoMoreInteractions(mock) 或 verifyZeroInteractions(mock1, mock2)。
  • 使用 JMockit,您只需编写 new VerificationsInOrder() 或 new FullVerifications() 而不是 new Verifications()(或 new FullVerificationsInOrder() 以结合这两个要求)。无需指定涉及哪些模拟对象。没有额外的模拟 API 调用。作为奖励,通过在有序验证块内调用 unverifiedInvocations(),您可以执行在 Mockito 中根本不可能的与订单相关的验证。

最后,与其他模拟工具包相比,JMockit 测试工具包具有更广泛的范围更远大的目标,以提供完整和复杂的开发人员测试解决方案。一个好的模拟 API,即使没有人为的限制,也不足以有效地创建测试。与 IDE 无关、易于使用且集成良好的代码覆盖工具也是必不可少的,而这正是 JMockit Coverage 旨在提供的。随着测试套件规模的扩大,开发人员测试工具集的另一部分将变得更加有用,这是在对生产代码进行本地化更改后逐步重新运行测试的能力;这也包含在 Coverage 工具中。

(当然,来源可能有偏见,但是……)

我会说选择JMockit。当您无法控制要测试的类(或者由于兼容性原因等原因无法破坏它)时,它是最容易使用、最灵活的,并且适用于几乎所有情况,甚至是困难的情况和场景。

我对 JMockit 的体验非常积极。

于 2011-06-22T11:36:05.623 回答
24

我使用过 Mockito 和 JMockit,我对它们的体验是:

  • 模拟:

    • 隐式模拟(-> 更好的可用性,但有无法检测到模拟上不允许的方法调用的危险)
    • 显式验证
  • 易模拟:

    • 显式嘲弄
    • 隐式验证
  • JMockit:

    • 支持两者
  • 此外,JMockit 的其他好处:

    • 如果您正在模拟静态方法/构造函数等(例如在没有 UT 的情况下扩展非常旧的遗留代码库),您将有两种选择:1)带有 Powermock 扩展的 Mockito/EasyMock 或 2)Jmockit
    • 内置覆盖率报告

我个人更喜欢 JMockit,我认为它功能更丰富、更灵活,但需要更陡峭的学习曲线。通常有多种方法可以实现相同的模拟效果,并且在设计模拟时需要更加小心。

于 2011-09-29T03:36:11.377 回答
15

我使用 jMockit只是因为它是 Deencapsultation.class 中的反射库。我实际上很喜欢 Mockito 的风格,但我拒绝更改我的代码并弄乱我的 API,以便有限的测试框架可以解决它。而且我喜欢测试我的所有代码,所以不能轻易测试私有方法的框架不是我想要使用的。

我被这篇文章迷住了

经过一段(公认的大)学习曲线后,jMockit 现在是我的主要模拟单元测试框架。

于 2011-01-24T12:43:03.197 回答
4

为了轻松测试我们的遗留代码库(有大量静态方法调用等),JMockit 非常宝贵。[我博客上一篇文章的无耻插件]

于 2010-11-30T19:16:59.133 回答
0

我个人更喜欢EasyMock
在漂亮、正常和严格的模拟控制之间切换的能力是我最喜欢的功能之一。

于 2010-11-05T12:34:58.057 回答