56

一种思考方式是:如果我们关心代码的设计,那么 EasyMock 是更好的选择,因为它通过其期望概念向您提供反馈。

如果我们关心测试的可维护性(更容易阅读、编写和较少受变化影响的脆弱测试),那么 Mockito 似乎是一个更好的选择。

我的问题是:

  • 如果您在大型项目中使用过 EasyMock,您是否发现您的测试更难维护?
  • Mockito 的限制是什么(除了 endo 测试)?
4

5 回答 5

123

我不会争论这些框架的测试可读性、大小或测试技术,我相信它们是相等的,但是通过一个简单的例子我会告诉你它们的区别。

给定:我们有一个类负责在某处存储一些东西:

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;
    }
}

我们要测试它:

模拟:

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())));
    }
}

易模拟:

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 常量的末尾添加了分隔符

现在的测试结果如何?首先,这两个测试都会失败,但会出现不同的错误消息:

易模拟:

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)

模拟:

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 在一行中混合了两种职责——存根和验证。这就是为什么当出现问题时很难理解是哪个部分导致了故障。

当然你可以告诉我 - 只需在断言之前更改测试并移动验证。哇,你是认真的吗,开发人员应该记住一些由模拟框架强制执行的魔法命令?

顺便说一句,它无济于事:

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 更好?这个框架不会在一个地方混合两种职责,当你的测试失败时,你会很容易理解为什么。

于 2011-12-26T03:26:55.647 回答
52

如果我们关心代码的设计,那么 Easymock 是更好的选择,因为它通过其期望概念向您提供反馈

有趣的。我发现“期望的概念”使许多开发人员在测试中投入了越来越多的期望,只是为了满足 UnexpectedMethodCall 问题。它如何影响设计?

更改代码时测试不应中断。当功能停止工作时,测试应该中断。如果有人喜欢在发生任何代码更改时中断测试,我建议编写一个断言 java 文件的 md5 校验和的测试:)

于 2010-11-07T18:35:59.903 回答
31

我是一名 EasyMock 开发人员,所以有点偏心,但我当然在大型项目中使用过 EasyMock。

我的观点是 EasyMock 测试确实会偶尔中断。EasyMock 强制你对你所期望的做一个完整的记录。这需要一些纪律。您应该真正记录预期的内容,而不是测试方法当前需要的内容。例如,如果在模拟中调用方法的次数无关紧要,不要害怕使用andStubReturn. 另外,如果您不关心参数,请使用anyObject()等等。在 TDD 中思考可以对此有所帮助。

我的分析是,EasyMock 测试会更频繁地中断,但 Mockito 测试不会在你想要的时候中断。我更喜欢打破我的测试。至少我知道我的发展对我有什么影响。这当然是我个人的观点。

于 2010-05-20T08:48:05.847 回答
7

我认为你不必太在意这个。Easymock 和 Mockito 都可以配置为“严格”或“好”,唯一的区别是默认情况下 Easymock 是严格的,而 Mockito 是好的。

与所有测试一样,没有硬性规定,您需要平衡测试信心与可维护性。我通常会发现某些功能或技术领域需要高度的信心,我会使用“严格”的模拟。例如,我们可能不希望 debitAccount() 方法被多次调用!然而,在其他情况下,模拟实际上只是一个存根,因此我们可以测试代码的真正“肉”。

在 Mockito 生命周期的早期,API 兼容性是一个问题,但现在有更多工具支持该框架。Powermock(个人最喜欢的)现在有一个 mockito 扩展

于 2010-07-28T10:03:24.863 回答
5

老实说,我更喜欢mockito。将 EasyMock 与 unitils 结合使用通常会导致异常,例如 IllegalArgumentException: not an interface 以及 MissingBehaviorExceptions。在这两种情况下,尽管代码和测试代码都很好。似乎 MissingBehaviorException 是由于使用 createMock 创建的模拟对象(使用类扩展!!)确实产生了这个错误。使用@Mock 时,它确实有效!我不喜欢这种误导行为,对我来说,这清楚地表明它的开发人员不知道他们在做什么。一个好的框架应该总是易于使用且不模棱两可。IllegalArgumentException 也是由于 EasyMock 内部的一些混合。另外,录音不是我想做的。我想测试我的代码是否抛出异常并返回预期结果。这与代码覆盖率相结合对我来说是正确的工具。每当我将 1 行代码放在前一行代码的上方或下方时,我不希望我的测试中断,因为这样可以提高性能左右。使用 mockito 没问题。使用 EasyMock,即使代码没有损坏,它也会导致测试失败。那很不好。它需要时间,因此需要金钱。您想测试预期的行为。你真的在乎事情的顺序吗?我想在极少数情况下你可能会。然后使用 Easymock。在其他情况下,我认为您将花费更少的时间使用 mockito 来编写测试。每当我将 1 行代码放在前一行代码的上方或下方时,我不希望我的测试中断,因为这样可以提高性能左右。使用 mockito 没问题。使用 EasyMock,即使代码没有损坏,它也会导致测试失败。那很不好。它需要时间,因此需要金钱。您想测试预期的行为。你真的在乎事情的顺序吗?我想在极少数情况下你可能会。然后使用 Easymock。在其他情况下,我认为您将花费更少的时间使用 mockito 来编写测试。每当我将 1 行代码放在前一行代码的上方或下方时,我不希望我的测试中断,因为这样可以提高性能左右。使用 mockito 没问题。使用 EasyMock,即使代码没有损坏,它也会导致测试失败。那很不好。它需要时间,因此需要金钱。您想测试预期的行为。你真的在乎事情的顺序吗?我想在极少数情况下你可能会。然后使用 Easymock。在其他情况下,我认为您将花费更少的时间使用 mockito 来编写测试。然后使用 Easymock。在其他情况下,我认为您将花费更少的时间使用 mockito 来编写测试。然后使用 Easymock。在其他情况下,我认为您将花费更少的时间使用 mockito 来编写测试。

亲切的问候劳伦斯

于 2012-07-10T14:39:52.250 回答