有一种思考方式是:如果我们关心代码的设计,那么EasyMock是更好的选择,因为它通过期望的概念向您提供反馈。
如果我们关心测试的可维护性(易于阅读、编写和具有较少的易变磨损测试),那么Mockito似乎是更好的选择。
我的问题是:
- 如果你在大型项目中使用了EasyMock,你是否发现你的测试难以维护?
- 除了端到端测试之外,Mockito有哪些限制?
有一种思考方式是:如果我们关心代码的设计,那么EasyMock是更好的选择,因为它通过期望的概念向您提供反馈。
如果我们关心测试的可维护性(易于阅读、编写和具有较少的易变磨损测试),那么Mockito似乎是更好的选择。
我的问题是:
我不会争论这些框架的测试可读性、大小或测试技术,我认为它们是相等的,但是在一个简单的例子中,我将向您展示它们之间的区别。
假设我们有一个类,负责在某个地方存储一些东西:
public class Service {
public static final String PATH = "path";
public static final String NAME = "name";
public static final String CONTENT = "content";
private FileDao dao;
public void doSomething() {
dao.store(PATH, NAME, IOUtils.toInputStream(CONTENT));
}
public void setDao(FileDao dao) {
this.dao = dao;
}
}
我们想要测试它:
Mockito:
public class ServiceMockitoTest {
private Service service;
@Mock
private FileDao dao;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
service = new Service();
service.setDao(dao);
}
@Test
public void testDoSomething() throws Exception {
// given
// when
service.doSomething();
// then
ArgumentCaptor<InputStream> captor = ArgumentCaptor.forClass(InputStream.class);
Mockito.verify(dao, times(1)).store(eq(Service.PATH), eq(Service.NAME), captor.capture());
assertThat(Service.CONTENT, is(IOUtils.toString(captor.getValue())));
}
}
EasyMock:
public class ServiceEasyMockTest {
private Service service;
private FileDao dao;
@Before
public void setUp() {
dao = EasyMock.createNiceMock(FileDao.class);
service = new Service();
service.setDao(dao);
}
@Test
public void testDoSomething() throws Exception {
// given
Capture<InputStream> captured = new Capture<InputStream>();
dao.store(eq(Service.PATH), eq(Service.NAME), capture(captured));
replay(dao);
// when
service.doSomething();
// then
assertThat(Service.CONTENT, is(IOUtils.toString(captured.getValue())));
verify(dao);
}
}
如您所见,这两个测试用例几乎相同,并且都通过了。 现在,让我们想象一下其他人更改了服务实现并尝试运行这些测试。
新的服务实现:
dao.store(PATH + separator, NAME, IOUtils.toInputStream(CONTENT));
在PATH常量的末尾添加了分隔符。
现在测试结果会是什么样子?首先,两个测试都会失败,但出现不同的错误消息:
EasyMock:
java.lang.AssertionError: Nothing captured yet
at org.easymock.Capture.getValue(Capture.java:78)
at ServiceEasyMockTest.testDoSomething(ServiceEasyMockTest.java:36)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
Mockito:
Argument(s) are different! Wanted:
dao.store(
"path",
"name",
<Capturing argument>
);
-> at ServiceMockitoTest.testDoSomething(ServiceMockitoTest.java:34)
Actual invocation has different arguments:
dao.store(
"path\",
"name",
java.io.ByteArrayInputStream@1c99159
);
-> at Service.doSomething(Service.java:13)
为什么 EasyMock 测试中的结果没有被捕获?store 方法没有执行吗?等等,它执行了吧,为什么 EasyMock 对我们说谎了?
这是因为 EasyMock 把存根和验证两个责任混合在一条语句中。所以当出现问题时,很难理解哪个部分导致了失败。
当然您可以告诉我 - 只需更改测试并将 verify 移到断言之前。哇,您是认真的吗,开发人员应记住由模拟框架强制执行的某些神奇顺序吗?
顺便说一下,这是无法解决问题的:
java.lang.AssertionError:
Expectation failure on verify:
store("path", "name", capture(Nothing captured yet)): expected: 1, actual: 0
at org.easymock.internal.MocksControl.verify(MocksControl.java:111)
at org.easymock.classextension.EasyMock.verify(EasyMock.java:211)
尽管如此,它告诉我方法未被执行,但实际上是执行了的,只是使用了其他参数。
为什么Mockito更好?这个框架不会在一个地方混合两个职责,当你的测试失败时,你将很容易理解失败的原因。
EasyMock.createMock(FileDao.class)
),然后验证顺序并不重要,异常非常清晰:
Unexpected method call store("path\", "name", java.io.ByteArrayInputStream@58651fd0): store("path", "name", capture(Nothing captured yet)): expected: 1, actual: 0
- Ian Jonesjava.lang.AssertionError: Nothing captured yet
这种不可理解的错误信息,我会很生气。仅凭这个错误信息就足以说明EasyMock并没有为开发人员的体验进行优化。 - HenryNguyen如果我们关心代码设计,那么Easymock是更好的选择,因为它通过期望概念给出反馈。
有趣。我发现,“期望概念”让许多开发人员在测试中加入越来越多的期望,只是为了满足UnexpectedMethodCall问题。这会影响到设计吗?
测试不应该因为你修改了代码而失败,它应该是在功能停止工作时才会失败。如果一个人想让测试在任何代码更改时都失败,我建议编写一个断言Java文件md5校验和的测试 :)
我是一名EasyMock开发者,所以有些偏见,但我在大型项目上也使用过EasyMock。
我的观点是,EasyMock测试确实会偶尔出错。EasyMock强制你完全记录预期的内容。这需要一定的纪律性。你应该记录期望的内容,而不是被测试方法当前需要的内容。例如,如果不关心模拟对象上调用方法的次数,请放心地使用andStubReturn
。同样地,如果不关心参数,请使用anyObject()
等。考虑TDD思想可以帮助你做到这一点。
我的分析是,EasyMock测试会更频繁地失败,而Mockito的测试则会在你希望它们失败时不会失败。我更喜欢我的测试会失败。至少我知道了我的开发产生了什么影响。当然,这只是我的个人观点。
我认为你不必过于担心这个问题。Easymock和Mockito都可以配置为“严格”或“友好”,唯一的区别是默认情况下Easymock是严格模式,而Mockito是友好模式。
与所有测试一样,没有硬性规定,需要在测试可靠性和可维护性之间取得平衡。通常我发现某些功能或技术领域需要高度可靠性,因此我会使用“严格”的mocks。例如,我们可能不希望debitAccount()方法被调用超过一次!但是,在其他情况下,mock只是一个存根,因此我们可以测试代码的真正“核心”。
在Mockito的早期生命周期中,API兼容性是一个问题,但现在更多的工具支持该框架。Powermock(个人喜爱)现在有一个mockito扩展。
说实话,我更喜欢Mockito。我之前一直使用unitils和EasyMock的组合,但这两者经常会导致异常,如IllegalArgumentException: not an interface和MissingBehaviorExceptions。不过,在这些情况下,我的代码和测试代码都是完全正确的。看起来,MissingBehaviorException是由于使用createMock(使用类扩展!!)创建的模拟对象引起的。当使用@Mock时,它确实有效!我不喜欢那种误导性的行为,对我来说,这清楚地表明了开发人员并不知道他们在做什么。一个好的框架应该始终易于使用且不含糊不清。IllegalArgumentException也是由于EasyMock内部的某些混乱原因导致的。此外,录制不是我想要做的事情。我想测试我的代码是否会抛出异常以及是否返回预期结果。结合代码覆盖率来看,这就是适合我的工具。我不希望我的测试因为在上一行或下一行加入1行代码而失败,因为这会提高性能等原因。使用Mockito没有问题,但使用EasyMock则会导致测试失败,即使代码没有错误。那很糟糕,浪费时间和金钱。你想测试预期的行为,你真的关心事情的顺序吗?我想在极少数情况下可能会关心,那就使用Easymock。在其他情况下,我认为你会花更少的时间使用Mockito来编写你的测试。
顺祝商祺, 劳伦斯