2019年9月更新:Spring Boot(默认情况下)支持的唯一模拟框架是Mockito。如果你使用Spring,答案是显而易见的。
我会说竞争是在JMockit和PowerMock之间,然后是Mockito。
我会留下“普通”jMock和EasyMock,因为它们只使用代理和CGLIB,不像较新的框架那样使用Java 5工具。
jMock也有4年多没有稳定版本了。jMock 2.6.0 需要 2 年时间才能从 RC1 发展到 RC2,然后再过 2 年才能真正发布。
关于 Proxy & CGLIB 与 Instrumentation:
(EasyMock和jMock)基于java.lang.reflect.Proxy,这需要一个接口来实现。此外,它们还支持通过 CGLIB 子类生成为类创建模拟对象。因此,所述类不能是最终的,只能模拟可重写的实例方法。然而,最重要的是,在使用这些工具时,受测代码的依赖关系(即,被测给定类所依赖的其他类的对象)必须由测试控制,以便可以将模拟实例传递给这些依赖关系的客户端。因此,不能简单地使用我们要为其编写单元测试的客户端类中的新运算符来实例化依赖项。
最终,传统模拟工具的技术限制对生产代码施加了以下设计限制:
- 每个可能需要在测试中模拟的类都必须实现一个单独的接口,或者不是最终的。
- 要测试的每个类的依赖项必须通过可配置的实例创建方法(工厂或服务定位器)获取,或者公开以供依赖项注入。否则,单元测试将无法将依赖项的模拟实现传递给所测试单元。
- 由于只能模拟实例方法,因此要进行单元测试的类不能在其依赖项上调用任何静态方法,也不能使用任何构造函数实例化它们。
以上内容抄自 http://jmockit.org/about.html 。此外,它以多种方式比较了自身(JMockit),PowerMock和Mockito:
现在还有其他用于Java的模拟工具,它们也克服了传统工具的局限性,在PowerMock,jEasyTest和MockInject之间。最接近JMockit功能集的是PowerMock,所以我将在这里简要评估它(此外,另外两个更受限制,似乎不再积极开发)。
JMockit vs PowerMock
- 首先,PowerMock没有提供完整的模拟API,而是作为另一个工具的扩展,目前可以是EasyMock或Mockito。对于这些工具的现有用户来说,这显然是一个优势。
- 另一方面,JMockit提供了全新的API,尽管它的主要API(期望)类似于EasyMock和jMock。虽然这创造了更长的学习曲线,但它也允许JMockit提供更简单,更一致,更易于使用的API。
- 与JMockit期望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 中提供的某些功能(如对模拟等于和哈希码、重写方法等的支持)目前在 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的显式调用,以便在记录(...)和验证(...))阶段之间分离代码。这意味着对测试代码中模拟对象的任何调用也需要调用模拟 API。此外,这通常会导致重复的 when(...) 和 verify(mock)...调用。
- 使用JMockit,不存在类似的调用。当然,我们有新的NonStrictExpectations()和新的Verifications()构造函数调用,但它们在每个测试(通常)中只发生一次,并且与模拟方法和构造函数的调用完全分开。
- Mockito API 在用于调用模拟方法的语法中包含几个不一致之处。在记录阶段,我们有像 when(mock.mockedMethod(args))这样的调用...而在验证阶段,相同的调用将编写为verify(mock).mockedMethod(args)。请注意,在第一种情况下,对 mockedMethod 的调用是直接在 mock 对象上进行的,而在第二种情况下,它是在 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();result = new RuntimeException();.
- 在使用Mockito间谍时还发生了另一个不一致:允许在spied实例上执行真实方法的“模拟”。例如,如果spy指的是一个空的列表,那么你需要写doReturn(“foo”)而不是写when(spy.get(0)).thenReturn(“foo”).when(spy).get(0)。使用JMockit,动态模拟功能提供了与间谍类似的功能,但没有这个问题,因为真正的方法只在重放阶段执行。
- 在 EasyMock 和 jMock(Java 的第一个模拟 API)中,重点完全放在记录模拟方法的预期调用上,对于(默认情况下)不允许意外调用的模拟对象。这些 API 还为允许意外调用的模拟对象提供允许调用的记录,但这被视为二等功能。此外,使用这些工具,无法在执行所测试的代码后显式验证对模拟的调用。所有这些验证都是隐式自动执行的。
- 在Mockito(以及Unitils Mock)中,采取了相反的观点。所有在测试期间可能发生的模拟对象的调用,无论是否记录,都是允许的,永远不会预料到。验证是在执行所测试代码后显式执行的,而不是自动执行的。
- 这两种方法都过于极端,因此不是最佳的。JMockit期望和验证是唯一允许开发人员为每个测试无缝选择严格(默认预期)和非严格(默认允许)模拟调用的最佳组合的API。
- 更清楚的是,Mockito API具有以下缺点。如果您需要验证在测试期间是否发生了对非 void 模拟方法的调用,但测试需要该方法的返回值与返回类型的默认值不同,则 Mockito 测试将具有重复的代码:记录阶段的 when(mock.someMethod()).thenReturn(xyz) 调用,以及验证阶段的 verify(mock).someMethod()。使用JMockit,始终可以记录严格的期望,而不必明确验证。或者,可以为任何记录的非严格期望指定调用计数约束(times = 1)(使用 Mockito,此类约束只能在 verify(mock, constraint) 调用中指定)。
- Mockito在按顺序验证和完全验证(即检查对模拟对象的所有调用是否都经过显式验证)方面语法很差。在第一种情况下,需要创建一个额外的对象,并调用以验证它:InOrder inOrder = inOrder(mock1, mock2, ...)。在第二种情况下,需要进行像verifyNoMoreInteractions(mock)或verifyZeroInteractions(mock1,mock2)这样的调用。
- 使用JMockit,您只需编写新的VerificationsInOrder()或新的FullVerifications()而不是新的Verifications()(或新的FullVerificationsInOrder()来结合这两个要求)。无需指定涉及哪些模拟对象。没有额外的模拟 API 调用。作为奖励,通过在有序验证块内调用未验证的调用(),您可以执行与订单相关的验证,这在Mockito中根本不可能。
最后,JMockit测试工具包比其他模拟工具包具有更广泛的范围和更雄心勃勃的目标,以提供完整而复杂的开发人员测试解决方案。一个好的模拟API,即使没有人为的限制,也不足以有效地创建测试。一个与IDE无关的、易于使用的、集成良好的代码覆盖率工具也是必不可少的,这就是JMockit覆盖率旨在提供的工具。开发人员测试工具集的另一部分将随着测试套件大小的增长而变得更加有用,这是在对生产代码进行本地化更改后增量重新运行测试的能力;这也包含在覆盖范围工具中。
(当然,来源可能有偏见,但很好...)
我会说和JMockit一起去。它是最容易使用,最灵活的,并且几乎适用于所有情况,甚至是困难的情况和场景,当您无法控制要测试的类时(或者由于兼容性原因等原因您无法破坏它)。
我在JMockit的经历非常积极。