我正在尝试使用Mockito来模拟“Reader”类型类。想想数据流读取器,它有读取各种数据类型的方法,并在每次读取后推进内部指针。
public interface Reader {
int readInt();
short readShort();
}
正在测试的类从数据流中读取各种数据结构。例如,
public class Somethings {
public List<Something> somethings;
public Somethings(Reader reader) {
somethings = new List<Something>();
int count = reader.readInt();
for (int i=0; i<count; i++) {
somethings.add(readSomething(reader));
}
}
private Something readSomething(Reader reader) {
int a = reader.readInt();
short b = reader.readShort();
int c = reader.readInt();
return new Something(a, b, c);
}
}
最后,我有我的考试:
public class SomethingsTest {
@Test
public void testSomethings() {
Reader reader = Mockito.mock(Reader.class);
readCount(reader, 2);
readSomething(reader, 1, 2, 3);
readSomething(reader, 4, 5, 6);
Somethings somethings = new Somethings(reader);
Assert.assertEqual(2, somethings.size());
Assert.assertEquals(new Something(1, 2, 3), somethings.somethings.get(0));
Assert.assertEquals(new Something(4, 5, 6), somethings.somethings.get(1));
}
private void readCount(Reader reader, int count) {
when(reader.readInt()).thenReturn(count);
}
private void readSomething(Reader reader, int a, short b, int c) {
when(reader.readInt()).thenReturn(a);
when(reader.readShort()).thenReturn(b);
when(reader.readInt()).thenReturn(c);
}
}
不幸的是,这不起作用。 reader.readInt()始终为每次调用返回6。我明白为什么它会返回6.这不是我的问题。
我可以考虑两种方法来修复测试,但我并不特别喜欢这两种方法。
第一个选项是:
public class SomethingsTest {
@Test
public void testSomethings() {
Reader reader = Mockito.mock(Reader.class);
when(reader.readInt())
.thenReturn(2)
.thenReturn(1)
.thenReturn(3)
.thenReturn(4)
.thenReturn(6);
when(reader.readShort())
.thenReturn(2)
.thenReturn(5);
Somethings somethings = new Somethings(reader);
Assert.assertEqual(2, somethings.size());
Assert.assertEquals(new Something(1, 2, 3), somethings.somethings.get(0));
Assert.assertEquals(new Something(4, 5, 6), somethings.somethings.get(1));
}
}
这应该可行,但它非常单一和混乱。很难看出哪个结构的回归是哪个,因为它们都是混合的,没有结构。
我能想到的第二个选择是:
public class SomethingsTest {
@Test
public void testSomethings() {
Reader reader = Mockito.mock(Reader.class);
NewOngoingStubbing readIntStub = when(reader.readInt());
NewOngoingStubbing readShortStub = when(reader.readShort());
readCount(readIntStub, 2);
readSomething(readIntStub, readShortStub, 1, 2, 3);
readSomething(readIntStub, readShortStub, 4, 5, 6);
Somethings somethings = new Somethings(reader);
Assert.assertEqual(2, somethings.size());
Assert.assertEquals(new Something(1, 2, 3), somethings.somethings.get(0));
Assert.assertEquals(new Something(4, 5, 6), somethings.somethings.get(1));
}
private void readCount(NewOngoingStubbing readIntStub, int count) {
readIntStub.thenReturn(count);
}
private void readSomething(NewOngoingStubbing readIntStub,
NewOngoingStubbing readShortStub, int a, short b, int c) {
readIntStub.thenReturn(a);
readShortStub.thenReturn(b);
readIntStub.thenReturn(c);
}
}
这至少维护了原始的结构,但是必须为要在stubed对象上进行的每个方法调用传递一个单独的对象是......呃。
执行此测试的最简洁方法是什么?我在这里找不到一些选择吗?我可以利用哪些功能?我今晚刚开始使用Mockito ..所以我很可能会错过一些东西。
答案 0 :(得分:5)
Mockito本身就在继续进行。你的第一个例子很好,但这也应该有效:
when(reader.readInt()).thenReturn(2, 1, 3, 4, 6);
它的文档是here。
如果你有处理特别复杂的交互的东西,可以推出你自己的存根类。您可能会发现使用真实数据初始化一些假,然后使用它,提供了比Mockito更清晰的示例。如果是这样的话,那就明确一点。 Mockito是IMO最好的模拟框架,但有时候我还是自己动手。
答案 1 :(得分:2)
您可以创建一个类来处理它,例如
private static class ReaderStubber {
private final Reader reader = Mockito.mock(Reader.class);
private final NewOngoingStubbing readIntStub = when(reader.readInt());;
private final NewOngoingStubbing readShortStub = when(reader.readShort());;
public Reader getReader() {
return reader;
}
private void readCount(int count) {
readIntStub.thenReturn(count);
}
private void readSomething(int a, short b, int c) {
readIntStub.thenReturn(a);
readShortStub.thenReturn(b);
readIntStub.thenReturn(c);
}
}
但问题是,你真的需要和Mockito一起做吗?并非一切都应该被嘲笑。也许只是为测试实现一个存根Reader
,内部有一些List<Integer>
更好。
(编辑)
此外,如果这是可能的,也许您应该重新设计Reader
以使其不可变并返回一些NewOngoingReading
。经常(但不总是)难以测试的东西更适合重新设计。此外,您不需要处理同步。
答案 2 :(得分:2)
当Mockito的标准方法没有提供模拟您行为的机制时,您可以使用自己的Answer
。这是更多的工作,但提供了额外的灵活性。
根据您的具体要求,您可以创建一个Answer
,从数字列表中返回一个新元素,而不管请求类型(int
或short
)。变量readList
可以是您可以从用于设置结果的所有函数中访问的成员。
final List<Integer> readList = new ArrayList<>();
// ... Fill readList with answers
Answer answerFromList = new Answer() {
Object answer(InvocationOnMock invocation) {
// Remove and return first element
return readList.remove(0);
}
}
when(reader.readInt()).thenAnswer(answerFromList);
when(reader.readShort()).thenAnswer(answerFromList);
请注意,此Answer
与提供的ReturnElementsOf
非常相似,因此您也可以直接使用它。