6

我正在尝试使用 Mockito 来模拟“阅读器”类型的类。想想一个数据流阅读器,它具有读取各种数据类型并在每次读取后推进内部指针的方法。

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

这至少保持了原始的结构,但是必须为要对存根对象进行的每个方法调用传递一个单独的对象......呃。

执行此测试的最干净的方法是什么?我在这里缺少一些选择吗?我可以利用的一些功能?我今晚才开始使用 Mockito .. 所以我很可能会遗漏一些东西。

4

3 回答 3

7

Mockito 在本地进行持续的存根。您的第一个示例很好,但这也应该有效:

when(reader.readInt()).thenReturn(2, 1, 3, 4, 6);

它的文档在这里

如果你有处理特别复杂的交互的东西,可以推出你自己的存根类。你可能会发现用真实数据初始化一些假数据,然后使用它,提供了一个比 Mockito 更清楚的类如何协作的例子。如果是这种情况,请明确约定。Mockito 是 IMO 最好的模拟框架,但有时我还是会推出自己的。

于 2012-10-27T10:37:15.703 回答
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. 通常(但并非总是)难以测试的东西最好重新设计。此外,您不需要处理同步。

于 2012-10-27T10:43:37.090 回答
2

当 Mockito 的标准方法不提供模拟您的行为的机制时,您可以求助于实现自己的Answer. 这是更多的工作,但提供了额外的灵活性。

根据您的精确要求,您可以例如创建一个Answer从数字列表中返回一个新元素的方法,而不管请求类型(intshort)如何。该变量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,因此您也可以直接使用它。

于 2015-02-16T10:43:14.190 回答