在方法级别隔离单元测试和存根内部方法调用是否正确?

时间:2014-01-01 00:20:17

标签: java unit-testing junit mockito

我总是怀疑单位测试隔离是否应该在非常细微的内容上进行,例如对内部方法调用进行存根,请查看以下使用Java,JUnit和Mockito的示例。

package com.company.domain;

public class Foo {
    private FooHelper helper; 

    public setHelper(FooHelper helper) {
        this.helper = helper;
    }

    public Double method1(Integer a, Integer b) {
        ... // logic here
        Integer var = method2(a);  // internal call to method2
        ... // more logic here
    }

    protected Integer method2(Integer c) {
        ... // logic here and return an Integer
    }
}

这是测试类:

package com.company.domain;

@RunWith(MockitoJUnitRunner.class)    
public class FooTest {
    @Mock private FooHelper fooHelperMock; // mocking dependencies as usual
    @InjectMocks @Spy Foo foo; // this way we can stub internal calls

    @Test
    public void method1Test() {
        doReturn(2).when(foo).method2(1); //stub the internal method call
        ... // stub fooHelperMock method calls here

        Double result = foo.method1(1, 2);

        assertEquals(new Double(1.54), result);
        verify(foo, times(1)).method2(1);
        ... // verify fooHelperMock method calls here
    }

    @Test
    public void method2Test() {
        ... // stub fooHelper method calls here
        assertEquals(new Integer(5), foo.method2(3));
        ... // verify fooHelperMock method calls here
    }
}

我认为,即使使用内部调用,您也可以在方法级别上真正隔离测试中的代码。但是我找不到很多关于这被认为是好的做法的信息以及原因。

3 个答案:

答案 0 :(得分:9)

通常,测试类的内部函数调用会使您的测试过于脆弱。在单元测试时,你只想看到整个类正在做它应该做的事情。你应该故意忽略内部。

忽略内部有很多有用的好处。主要是,如果您更改方法 - 只要公共接口保持不变 - 您的测试仍然会通过。好极了。这是您希望单元测试具有的稳定性类型。脆弱的单元测试几乎比没有单元测试差。每次代码更改时都必须更改脆弱的测试,即使一切仍然有效。除了额外的工作外,这只能为你提供。

但也有例外。在下列情况下,我会考虑模拟内部函数调用:

1)内部呼叫从某个地方提取数据。例如数据库或大文件。这通常被称为“边界”测试。我们不想真正设置要测试的数据库或文件,所以我们只是将该函数分出来。但是,如果你发现自己这样做,这可能意味着你的类应该分成两个类,因为它做了太多不同的事情。相反,让自己成为一个专用的数据库类。

2)内部调用执行繁重的处理需要花费大量时间。在这种情况下,将函数分解而不是等待可能是有意义的。然而,这可能再一次表明你走错了路。您应该尝试找到一种方法来使事情更加分区,因此没有一个步骤需要那么长时间。

总之,我强烈建议您不要在方法级别进行测试。一个类应该是一个独特的单元。如果课程正常,一切都很好。这种方法可以为您提供安全所需的测试以及更改内容所需的灵活性。

@raspacorp

实际上,如果你只测试一个案例,有人可以硬编码var = 2然后通过你的测试用例。但是,设计测试用例以涵盖足够多的不同情况以合理地满足它的行为是正确的。

对于由于代码更改而导致测试发生变化的情况,这根本不是您想要的。您希望测试验证您获得的响应对于所有不同类型的案例都是正确的。只要响应正确,您就希望能够在不更改测试的情况下更改所有内容。

想象一个拥有数千个测试的大型系统,证明一切正常。现在想象一下,您希望进行巨大的优化更改,以使事情变得更快,但更改所有内容的存储和计算方式。您希望测试允许您做的只是更改代码(而不是测试)并让测试不断确认一切仍然有效。这就是单元测试的目的,而不是捕捉每一个可能的换行,而是验证所有的计算和行为都是正确的。

答案 1 :(得分:5)

article from Martin Fowler回答了您的问题。

你是个嘲笑者,但是有很多人不喜欢这种做法,而更喜欢古典主义的做法(例如Kent Beck)。

Roy Osherove says测试的目标是在失败的情况下确定导致问题的生产代码,并且考虑到这一点很明显,粒度越细越好。

对于初学者来说肯定是一个模仿者可能是压倒性的,但是一旦你习惯了Mocking库的机制,那么这些好处是无法实现的,而努力并不比其他方法高很多。

答案 2 :(得分:1)

我认为没有必要,我甚至会更进一步说你不应该这样做。您正在编写更多测试代码来测试较少的应用程序代码。一般来说,仅仅测试公共方法被认为是一种良好的做法,因为单元测试只应该考虑被测方法是否满足其合同(即应该做什么),而不是方法是如何实现的。如果你的私人方法有错误,你怎么知道?