我正在使用Mockito对业务对象进行单元测试。业务对象使用DAO,该DAO通常从DB获取数据。为了测试业务对象,我意识到使用单独的内存中DAO(将数据保存在HashMap中)比编写所有更容易
when(...).thenReturn(...)
声明。为了创建这样一个DAO,我首先对我的DAO接口进行了部分嘲讽,如下所示:
when(daoMock.getById(anyInt())).then(new Answer() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
int id = (Integer) invocation.getArguments()[0];
return map.get(id);
}
});
但我突然想到,只需自己实现一个全新的DAO实现(使用内存中的HashMap),甚至不使用Mockito(无需从InvocationOnMock对象中获取参数),并让测试的业务对象使用这个新的DAO,会更容易。
此外,我读到过部分嘲讽被认为是不好的做法。我的问题是:在我的情况下,我所做的是一种糟糕的做法吗?缺点是什么?对我来说,这似乎还可以,我想知道潜在的问题可能是什么。
我想知道为什么你需要你的假DAO由HashMap
支持。我想知道你的测试是否太复杂了。我非常喜欢用非常简单的测试方法来测试SUT行为的一个方面。原则上,这是"每个测试一个断言",尽管有时我会得到少量实际的assert
或verify
行,例如,如果我断言一个复杂对象的正确性。请阅读http://blog.astrumfutura.com/2009/02/unit-testing-one-test-one-assertion-why-it-works/或http://blog.jayfields.com/2007/06/testing-one-assertion-per-test.html以了解有关此原理的更多信息。
因此,对于每个测试方法,您不应该一遍又一遍地使用您的假DAO。可能只有一次,最多两次。因此,在我看来,有一个装满数据的大HashMap
要么是多余的,要么表明你的测试做得远远超出了它应该做的。对于每个测试方法,您实际上应该只需要一到两项数据。如果您使用DAO接口的Mockito mock设置这些测试,并将when ... thenReturn
放入测试方法本身,则每个测试都将是简单可读的,因为特定测试使用的数据将立即可见。
你可能还想了解一下"安排、行动、断言"模式(http://www.arrangeactassert.com/why-and-what-is-arrange-act-assert/和http://www.telerik.com/help/justmock/basic-usage-arrange-act-assert.html)在每个测试方法中实现这个模式时要小心,而不是将它的不同部分分散在测试类中。
如果没有看到更多实际的测试代码,就很难知道还能给你什么其他建议。Mockito应该让嘲笑变得更容易,而不是更难;因此,如果你有一个测试,但你没有这样做,那么你是否在做一些非标准的事情当然值得一问。你所做的并不是"部分嘲笑",但在我看来,这确实有点测试的味道。尤其是因为它将你的许多测试方法结合在一起——问问自己,如果你必须更改HashMap
中的一些数据,会发生什么。
你可能会发现https://softwareengineering.stackexchange.com/questions/158397/do-large-test-methods-indicate-a-code-smell也很有用。
在测试我的类时,我经常使用Mockito制作的mock和fake的组合,这正是你所描述的。在你的情况下,我同意假实现听起来更好。
部分mock没有什么特别的问题,但它会让你更难确定什么时候调用真实对象,什么时候调用模拟方法——尤其是因为Mockito无法模拟最终方法。对原始类进行看似无辜的更改可能会更改部分mock的实现,从而导致测试停止工作。
如果你有灵活性,我建议提取一个接口,公开你需要调用的方法,这将使你更容易选择mock或false。
要编写一个假的,请使用一个简单的类(如果你愿意,可以嵌套在测试中)在没有Mockito的情况下实现这个小接口。这将使人们很容易看到正在发生的事情;不利的一面是,如果你写了一个非常复杂的Fake,你可能会发现你也需要测试Fake。如果你有很多测试可以利用一个好的Fake实现,那么这可能值得额外的代码。
我强烈推荐Martin Fowler的一篇文章"Mocks are not Stubs"(因其著作《重构》而闻名)。他复习了不同类型替身的名字,以及它们之间的区别。