2010-05-19 68 views
41

思考这个问题的一种方式是:如果我们关心代码的设计,那么EasyMock就是更好的选择,因为它通过其期望的概念向您提供反馈。EasyMock vs Mockito:设计与可维护性?

如果我们关心测试的可维护性(易于阅读,编写,并且不会受到更改影响较小的脆性测试),那么Mockito似乎是更好的选择。

我的问题是:

  • 如果您在大型项目中使用EasyMock的,你发现你的测试是难以维持?
  • Mockito(Endo测试除外)的局限性是什么?

回答

26

我是一个EasyMock开发者,所以有点偏,但是我已经在大型项目中使用了EasyMock。

我的观点是EasyMock测试确实会在短时间内破坏一次。 EasyMock强迫你做一个你期望的完整记录。这需要一些纪律。你应该记录什么是预期的,而不是测试方法目前需要什么。例如,如果一个方法在模拟上调用了多少时间并不重要,那么不要害怕使用andStubReturn。另外,如果您不关心参数,请使用anyObject()等。 TDD的思考可以帮助解决这个问题。

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

+1

是的,我一直在思考同一个:用的Mockito(和Unitils模拟,类似的嘲讽API)是编写继续愉快地时,他们不应该通过试验容易得多。我怀疑这可能是为什么类似Mockito的应用程序接口(API)过度“松散”测试的主要原因,通常被认为“更容易”。 – 2010-06-08 18:06:54

+3

我有兴趣看到一个对比这两种方法的例子... – Armand 2011-01-28 15:23:53

7

我不认为你应该太在意这件事。 Easymock和Mockito都可以配置为'strict'或'nice',唯一的区别是默认情况下Easymock是严格的,Mockito很好。

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

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

42

如果我们关心代码的设计则EasyMock的是更好的选择,因为它通过它的预期

有趣的概念给出了反馈给您。我发现“期望概念”使得许多开发人员在测试中提出了更多的期望,只是为了满足意外的方法调用问题。它如何影响设计?

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

94

我不会争论这些框架的测试可读性,大小或测试技术,我相信他们是平等的,但在一个简单的例子中,我会告诉你不同之处。

考虑:我们有一类是负责地方存放东西:

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

分离器在路径的终点不断

加入如何测试结果会像现在呢?首先这两个测试会失败,但使用不同的错误信息:

了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", 
    [email protected] 
); 
-> at Service.doSomething(Service.java:13) 

在EasyMock的测试发生了什么事,为什么结果还是没捕捉到?商店方法没有被执行,但是等一下,那是为什么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更好?这个框架不会将两个职责混合在一个地方,当你的测试失败时,你会很容易理解为什么。

+0

我首先尝试mockito卖出。测试可维护性已经足够混乱,而模拟框架无法应对。 – Gary 2012-10-26 03:10:41

+0

我意识到这是旧的......但是,你在这里混合验证和存根过程而不是EasyMock。在验证之前做出断言是一种糟糕的做法,因此您可以实际删除'assertThat'调用,因为验证会为您选择:“FileStore.dao(”path“,”name“,capture(Nothing Captured yet)):预期1,实际:0“。 我会承认Mockito的信息在这里更加清晰,但在这个太具体的例子中,这并不足以影响某人对EasyMock的决定! – 2015-12-16 12:34:51

+0

我不知道我是如何在EasyMock中混合一些东西的,这是它的工作方式,所以如果你有一个很好的反例,我会很高兴看到它。我认为这是一个个人观点,不管这个例子是否足够影响这些框架的决策,我很高兴很多人认为它是有价值的。 – 2015-12-16 22:38:38

5

我更喜欢mockito说实话。一直在使用EasyMock和单元组合,两者的组合通常会导致异常,如IllegalArgumentException:不是接口以及MissingBehaviorExceptions。在这两种情况下,尽管代码和测试代码都非常好。看起来MissingBehaviorException是由于使用createMock创建的模拟对象(使用classextentions !!)确实产生了这个错误。当使用@Mock它确实工作!我不喜欢那种误导性的行为,对我而言,这清楚地表明它的开发者不知道他们在做什么。一个好的框架应该总是易于使用并且不含糊。 IllegalArgumentException也是由于EasyMock内部的混合。另外,录音不是我想要做的。我想测试我的代码是否会抛出异常,并返回预期的结果。这与代码覆盖率相结合对我来说是正确的工具。我不想让我的测试每当我把一行代码放在上一行或下一行时就中断,因为这会提高性能等等。随着mockito这是没有问题的。使用EasyMock,即使代码没有被破坏,也会导致测试失败。那很不好。它花费时间,因此花钱。你想测试预期的行为。你真的关心事物的顺序吗?我想在极少数情况下你可能会。然后使用Easymock。在其他情况下,我认为使用mockito编写测试的时间会少得多。

亲切的问候 劳伦斯