EasyMock vs Mockito:设计 vs 可维护性? [关闭]
Posted
技术标签:
【中文标题】EasyMock vs Mockito:设计 vs 可维护性? [关闭]【英文标题】:EasyMock vs Mockito: design vs maintainability? [closed] 【发布时间】:2011-02-21 07:54:27 【问题描述】:对此的一种思考方式是:如果我们关心代码的设计,那么 EasyMock 是更好的选择,因为它通过其期望概念向您提供反馈。
如果我们关心测试的可维护性(更易于阅读、编写和较少受更改影响的脆弱测试),那么 Mockito 似乎是一个更好的选择。
我的问题是:
如果您在大型项目中使用过 EasyMock,您是否发现您的测试更难维护? Mockito 有哪些限制(除了 endo 测试)?【问题讨论】:
这对我来说毫无疑问。嘲笑任何一天。 EasyMock(带有这种奇怪的 replay() 废话)非常不直观。使用简单的模拟就像在你的眼睛里放一个音叉。 【参考方案1】:我不会争论这些框架的测试可读性、大小或测试技术,我相信它们是相同的,但我将通过一个简单的示例向您展示它们的区别。
假设:我们有一个类负责在某处存储一些东西:
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);
如您所见,这两个测试几乎相同,并且都通过了。 现在,让我们假设其他人更改了 Service 实现并尝试运行测试。
新服务实施:
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 在一行中混合了两种职责 - 存根和验证。这就是为什么当出现问题时很难理解是哪个部分导致了故障。
你当然可以告诉我——在断言之前更改测试并移动验证。哇,你是认真的吗,开发人员应该记住一些由模拟框架强制执行的魔法命令?
顺便说一句,它没有帮助:
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 更好?这个框架不会在一个地方混合两种职责,当你的测试失败时,你会很容易理解为什么。
【讨论】:
我很喜欢先尝试 mockito。如果没有模拟框架的阻碍,测试的可维护性就足够混乱了。 我意识到这是旧的......但是,你在这里混合了验证和存根过程,而不是 EasyMock。在验证之前做出断言是不好的做法 - 因此您实际上可以删除“assertThat”调用,因为验证会为您选择它:“FileStore.dao("path", "name", capture(Nothing Captured)):预期 1,实际:0"。我承认 Mockito 的信息在这里更清晰,但在这个过于具体的例子中,这还不足以影响人们对 EasyMock 的决定! 我不确定我是如何在这里为 EasyMock 混合的,这就是它的工作方式,所以如果你有一个很好的反例,我会很高兴看到它。我认为这个例子是否足以影响对这些框架的决策,这是个人意见的问题,我很高兴很多人发现它很有价值。 在这个例子中没有理由使用漂亮的模拟。您应该使用普通的模拟(即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
EasyMock 现在也有类似的@Mock 注解来自动创建模拟并将它们注入到被测类中。【参考方案2】:
如果我们关心代码的设计,那么 Easymock 是更好的选择,因为它通过其期望概念向您提供反馈
有趣。我发现“期望的概念”使许多开发人员在测试中投入了越来越多的期望,只是为了满足 UnexpectedMethodCall 问题。它对设计有何影响?
更改代码时不应中断测试。当功能停止工作时,测试应该中断。如果有人喜欢在发生任何代码更改时中断测试,我建议编写一个断言 java 文件的 md5 校验和的测试:)
【讨论】:
【参考方案3】:我是一名 EasyMock 开发人员,所以有点偏心,但我当然在大型项目中使用过 EasyMock。
我认为 EasyMock 测试确实会偶尔中断。 EasyMock 强制你对你所期望的做一个完整的记录。这需要一些纪律。您应该真正记录预期的内容,而不是测试方法当前需要的内容。例如,如果一个方法在模拟中被调用多少次并不重要,不要害怕使用andStubReturn
。另外,如果您不关心参数,请使用anyObject()
等等。在 TDD 中思考可以对此有所帮助。
我的分析是,EasyMock 测试会更频繁地中断,但 Mockito 测试不会在您希望它们时中断。我更喜欢打破我的测试。至少我知道我的发展对我有什么影响。这当然是我个人的观点。
【讨论】:
是的,我一直在想同样的事情:使用 Mockito(和 Unitils Mock,一个类似的模拟 API),编写在不应该通过时继续愉快地通过的测试要容易得多。我怀疑这可能是为什么类似 Mockito 的 API(它有助于创建过度“松散”的测试)通常被认为“更容易”的主要原因。 我很想看一个对比这两种方法的例子...... 我记得将测试代码复制粘贴到单元测试中,并将其“重新格式化”为模拟定义。只是因为它比手动注销要快。如果你开始写这样的测试,那就真的有问题了。一份经过测试的代码副本怎么可能成为一个好的单元测试? 严格方法的另一个问题是您需要从头开始设置每个单元测试(方法)中的每个模拟。虽然可以在 setup 方法中很大程度上设置一次“漂亮”的模拟(类似于“当 getName 被调用时,“Hugo”将被返回)。每个测试方法只需要写出它与其他测试方法的不同之处。因此,平均而言,您有一行额外的代码用于模拟,然后运行,然后验证您想要验证的任何内容。这实际上是我用模拟编写单元测试时最大的不同。 3 行单元测试。您将测试更多案例。【参考方案4】:我认为你不应该太担心这一点。 Easymock 和 Mockito 都可以配置为 'strict' 或 'nice' ,唯一的区别是默认情况下 Easymock 是严格的,而 Mockito 是 nice 的。
与所有测试一样,没有硬性规定,您需要平衡测试信心和可维护性。我通常会发现某些功能或技术领域需要高度的信心,我会使用“严格”的模拟。例如,我们可能不希望 debitAccount() 方法被多次调用!然而,在其他情况下,模拟实际上只不过是一个存根,因此我们可以测试代码的真正“肉”。
在 Mockito 生命周期的早期,API 兼容性是一个问题,但现在有更多工具支持该框架。 Powermock(个人最喜欢的)现在有一个 mockito 扩展
【讨论】:
【参考方案5】:说实话,我更喜欢 mockito。将 EasyMock 与 unitils 结合使用通常会导致异常,例如 IllegalArgumentException: not an interface 以及 MissingBehaviorExceptions。在这两种情况下,尽管代码和测试代码都很好。似乎 MissingBehaviorException 是由于使用 createMock 创建的模拟对象(使用类扩展!!)确实产生了这个错误。使用@Mock 时,它确实有效!我不喜欢这种误导行为,对我来说,这清楚地表明它的开发人员不知道他们在做什么。一个好的框架应该总是易于使用而不是模棱两可。 IllegalArgumentException 也是由于 EasyMock 内部的一些混合。另外,录音不是我想做的。我想测试我的代码是否抛出异常并返回预期结果。这与代码覆盖率相结合对我来说是正确的工具。每当我将 1 行代码放在前一行代码的上方或下方时,我不希望我的测试中断,因为这样可以提高性能左右。使用 mockito 没问题。使用 EasyMock,即使代码没有损坏,它也会导致测试失败。那很不好。它需要时间,因此需要金钱。您想测试预期的行为。你真的在乎事情的顺序吗?我想在极少数情况下你可能会。然后使用 Easymock。在其他情况下,我认为您使用 mockito 编写测试的时间会大大减少。
亲切的问候 劳伦斯
【讨论】:
以上是关于EasyMock vs Mockito:设计 vs 可维护性? [关闭]的主要内容,如果未能解决你的问题,请参考以下文章
PowerMock、EasyMock 和 Mockito 框架有啥区别? [复制]