Mockito 中模拟方法的单元测试最佳实践
假设我们有在类 A 中测试的方法,该方法调用类 B 中的方法。为了测试它,我们为 B 创建了模拟,然后验证它是否被调用。 verify(...) 是否足以进行单元测试,或者我需要断言测试方法的实际结果? 下面是澄清我的担忧的简化示例:
public class StringWriterATest {
StringWriterB b = mock(StringWriterB.class);
@Test
public void stringWriterATest() {
StringBuffer sb = new StringBuffer();
StringWriterA a = new StringWriterA();
a.stringWriterB=b;
a.append(sb);
ArgumentCaptor<StringBuffer> argument = ArgumentCaptor.forClass(StringBuffer.class);
verify(b).append(argument.capture());
assertEquals("StringWriterA", ((StringBuffer)argument.getValue()).toString());
//do we really need this or above is enough for proper unit test of method a.append(sb);
//assertEquals("StringWriterA_StringWriterB", sb);
}
}
public class StringWriterA {
public StringWriterB stringWriterB;
public void append(StringBuffer sb) {
sb.append("StringWriterA");
stringWriterB.append(sb);
}
}
class StringWriterB {
public void append(StringBuffer sb) {
sb.append("StringWriterB");
}
}
问候, 最大限度
Lets say we have method to test in class A that calls method from class B. To test it we created mock for B and then verify if it was called. Is verify(...) enough for unit test or I need assert actual result of tested method?
Below is simplified example to clarify my concern:
public class StringWriterATest {
StringWriterB b = mock(StringWriterB.class);
@Test
public void stringWriterATest() {
StringBuffer sb = new StringBuffer();
StringWriterA a = new StringWriterA();
a.stringWriterB=b;
a.append(sb);
ArgumentCaptor<StringBuffer> argument = ArgumentCaptor.forClass(StringBuffer.class);
verify(b).append(argument.capture());
assertEquals("StringWriterA", ((StringBuffer)argument.getValue()).toString());
//do we really need this or above is enough for proper unit test of method a.append(sb);
//assertEquals("StringWriterA_StringWriterB", sb);
}
}
public class StringWriterA {
public StringWriterB stringWriterB;
public void append(StringBuffer sb) {
sb.append("StringWriterA");
stringWriterB.append(sb);
}
}
class StringWriterB {
public void append(StringBuffer sb) {
sb.append("StringWriterB");
}
}
Regards,
Max
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
永远不需要同时模拟返回值和验证对象。
考虑一下:
StringWriterA
是被测试的类。因此,您肯定会想要使用断言来验证此类的行为。为此,您需要模拟一个依赖项StringWriterB
。您不想想要在
StringWriterA
测试中测试StringWriterB
,因此测试中StringWriterB
交互的任何断言在错误的地方。您必须假设
StringWriterB
的行为符合预期。您要么想要验证StringWriterA
是否正确调用了StringWriterB
(使用verify()
),要么想要模拟它预期行为并模拟返回值。如果您进行模拟,则验证是隐式的,因为如果未调用该方法,则不会返回模拟的返回值。
在您的情况下,
StringWriterA.append()
不会返回任何值,因此甚至只能进行验证。StringWriterB.append()
也可以工作,应该在它自己的stringWriterBTest
中进行类似的验证测试。注意: 明确地进行测试是件好事。由于测试方法永远不会在框架之外调用,因此永远不需要将它们键入,因此您可以使用比生产代码方法中更长的方法名称。一个很好的约定是:
即
stringWriterAShoulderAppendConstantAndDelegateToStringWriterB()
stringWriterAShouldThrowNullPointerExceptionWhenNullArgument()
当您的构建(持续集成)中出现测试失败时,您不必追查出了什么问题,方法名称会在失败处出现,您可以读取它确切地知道必须纠正哪些行为。
There is never a need to mock a return value and verify an object at the same time.
Consider this:
StringWriterA
is the class under test. Therefore you'll definitely want to use assertions to verify the behavior of this class. In order to do this, you mock out a dependency,StringWriterB
.You do not want to test
StringWriterB
in your test ofStringWriterA
, therefore any assertions ofStringWriterB
interactions in your test are in the wrong place.You must assume that
StringWriterB
is behaving as expected. You either want to verify thatStringWriterA
calledStringWriterB
correctly (usingverify()
) or you want to mock its expected behavior and mock the return values.If you mock, then the verify is implicit since the mocked return value will not be returned if the method is not called.
In your case,
StringWriterA.append()
does not return any value, so only a verify is even possible. ThatStringWriterB.append()
also works should have a similar verify test in astringWriterBTest
of its own.Note: It's nice to be explicit with tests. Since test methods are never called outside of a framework, there is never a need to type them out, so you can have much longer method names than in production code methods. A nice convention is:
<underTest>Should<Expected>[When]<Condition>()
i.e.
stringWriterAShouldAppendConstantAndDelegateToStringWriterB()
stringWriterAShouldThrowNullPointerExceptionWhenNullArgument()
When you have test failures in your build (continuous integration), then you don't have to hunt down what went wrong, the method name appears right by the failure and you can read it to know exactly what behavior must be fixed.
在您的示例中,
StringWriterB
不存储任何状态,并且append
方法很容易是静态的。在这种情况下,调用纯粹是副作用,不需要测试。但是,我怀疑您的真实代码要复杂得多。如果有另一个对象正在访问 StringWriterB,那么您可能需要模拟它,以防出现意外的调用。如果您希望将来扩展 B,您可能还需要添加 B 的验证 - 可能会存储来自追加调用的状态并具有访问器。
需要考虑的一件事是调用
StringWriterA.append()
的目的是什么。如果它的工作是附加字符串StringWriterAStringWriterB
那么这就是您应该测试的内容,并且不需要模拟。 StringWriterA 如何完成该任务并不重要。但是,如果其工作的一部分还包括调用StringWriterB.append()
方法,则可能需要模拟,除非您想在 A 的测试中测试StringWriterB
。我的 WRT 模拟经验法则是使用真实的对象,直到我不直接测试的对象的接线变得太毛茸茸或太脆弱。如果我觉得我的测试的很大一部分实际上是在测试其他对象,那么模拟将是一个好主意。
In your example,
StringWriterB
stores no state and theappend
method could easily be static. In that case then the call is purely a side effect and does not need to be tested.However, I suspect your real code is much more complex. If there is a of another object accessing
StringWriterB
then you maye want to mock it out in case there are unexpected calls to it. You also may want to add the verify of B if you expect it to be expanded in the future -- possibly storing state from the append call and having accessors.One thing to consider is what the purpose of the call to
StringWriterA.append()
is. If it's job is to append the stringStringWriterAStringWriterB
then that is what you should be testing and a mock is not necessary. HowStringWriterA
accomplishes that task is immaterial. If, however, part of its job is to also call theStringWriterB.append()
method then a mock may will be necessary unless you want to testStringWriterB
in A's test.My rule of thumb WRT mocks is to use real objects until the wiring for the objects I'm not directly testing gets too hairy or too brittle. If I feel like a good portion of my tests are in actuality testing other objects then mocks would be a good idea.
首先尝试向其他人展示您编写的测试。在我看来,这很难读。 您无法真正从中看出您正在测试什么行为。可以在此处找到有关如何使其更具可读性的简要介绍 如何编写干净、可测试的代码代码。
使用参数捕获器也是一种味道。有关如何避免这种情况的一些示例,请参见tdd 博客< /a>.
为了回答您的问题,
verify
用于验证类之间的交互。 它用于驱动代码的设计。结果(如果需要)应由when或given
在处指定>测试开始。有关如何使用模拟驱动设计的更多信息(
when
、given
、verify
...)以及模拟与存根有何不同,请参阅在这里找到:模拟不是存根。该示例使用 JMock 而不是 Mockito 来定义模拟,但它非常相似(它是关于概念,而不是您使用的实现和库的细节)。First of all try to show somebody the test you wrote. It is hard to read in my opinion. You can't really tell from it what behaviour you are testing. A brief intro for you how to make it a bit more readable can be found here How to Write Clean, Testable Code .
Using argument captors is also a smell. Some examples how to avoid it can be found on this tdd blog.
To answer you question,
verify
is used to verify interactions between classes. It is used to drive the design of your code. The result (if needed) should be specified by awhen
orgiven
at the beginning of your test.Further information how to drive your design with mocks (
when
,given
,verify
, ...) and how mocks are different to stubs can be found here: Mocks are not stubs. That example uses JMock not Mockito for defining mocks, but it is very similar (it is about the concepts, not the details of implementation and libraries you use).