Mockito 中模拟方法的单元测试最佳实践

发布于 2024-11-02 23:13:28 字数 1142 浏览 0 评论 0原文

假设我们有在类 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

辞取 2024-11-09 23:13:28

永远不需要同时模拟返回值和验证对象。

考虑一下:

StringWriterA 是被测试的类。因此,您肯定会想要使用断言来验证此类的行为。为此,您需要模拟一个依赖项 StringWriterB

不想想要在 StringWriterA 测试中测试 StringWriterB,因此测试中 StringWriterB 交互的任何断言在错误的地方

您必须假设 StringWriterB 的行为符合预期。您要么想要验证 StringWriterA 是否正确调用了 StringWriterB(使用 verify()),要么想要模拟它预期行为并模拟返回值。

如果您进行模拟,则验证是隐式的,因为如果未调用该方法,则不会返回模拟的返回值。

在您的情况下,StringWriterA.append() 不会返回任何值,因此甚至只能进行验证。 StringWriterB.append() 也可以工作,应该在它自己的 stringWriterBTest 中进行类似的验证测试。

注意: 明确地进行测试是件好事。由于测试方法永远不会在框架之外调用,因此永远不需要将它们键入,因此您可以使用比生产代码方法中更长的方法名称。一个很好的约定是:

Should[When]()

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 of StringWriterA, therefore any assertions of StringWriterB interactions in your test are in the wrong place.

You must assume that StringWriterB is behaving as expected. You either want to verify that StringWriterA called StringWriterB correctly (using verify()) 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. That StringWriterB.append() also works should have a similar verify test in a stringWriterBTest 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.

﹏半生如梦愿梦如真 2024-11-09 23:13:28

在您的示例中,StringWriterB 不存储任何状态,并且 append 方法很容易是静态的。在这种情况下,调用纯粹是副作用,不需要测试。

但是,我怀疑您的真实代码要复杂得多。如果有另一个对象正在访问 StringWriterB,那么您可能需要模拟它,以防出现意外的调用。如果您希望将来扩展 B,您可能还需要添加 B 的验证 - 可能会存储来自追加调用的状态并具有访问器。

需要考虑的一件事是调用 StringWriterA.append()目的是什么。如果它的工作是附加字符串 StringWriterAStringWriterB 那么这就是您应该测试的内容,并且不需要模拟。 StringWriterA 如何完成该任务并不重要。但是,如果其工作的一部分还包括调用 StringWriterB.append() 方法,则可能需要模拟,除非您想在 A 的测试中测试 StringWriterB

我的 WRT 模拟经验法则是使用真实的对象,直到我不直接测试的对象的接线变得太毛茸茸或太脆弱。如果我觉得我的测试的很大一部分实际上是在测试其他对象,那么模拟将是一个好主意。

In your example, StringWriterB stores no state and the append 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 string StringWriterAStringWriterB then that is what you should be testing and a mock is not necessary. How StringWriterA accomplishes that task is immaterial. If, however, part of its job is to also call the StringWriterB.append() method then a mock may will be necessary unless you want to test StringWriterB 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.

允世 2024-11-09 23:13:28

首先尝试向其他人展示您编写的测试。在我看来,这很难读。 您无法真正从中看出您正在测试什么行为。可以在此处找到有关如何使其更具可读性的简要介绍 如何编写干净、可测试的代码代码
使用参数捕获器也是一种味道。有关如何避免这种情况的一些示例,请参见tdd 博客< /a>.

为了回答您的问题verify 用于验证类之间的交互它用于驱动代码的设计。结果(如果需要)应由when或given处指定>测试开始

有关如何使用模拟驱动设计的更多信息(whengivenverify...)以及模拟与存根有何不同,请参阅在这里找到:模拟不是存根。该示例使用 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 a when or given 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).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文