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

ras*_*orp 2 java junit unit-testing 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
    }
}
Run Code Online (Sandbox Code Playgroud)

这是测试类:

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
    }
}
Run Code Online (Sandbox Code Playgroud)

我认为这样你就可以在方法级别上真正隔离测试代码,即使是内部调用也是如此.但是我找不到很多关于这个被认为是好习惯的信息以及为什么.

Dam*_*ack 9

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

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

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

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

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

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

@raspacorp

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

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

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


die*_*sis 5

Martin Fowler的这篇文章回答了你的问题.

你是一个嘲笑者,就像我一样,但是有很多人不喜欢这种方法而更喜欢古典主义的方法(例如Kent Beck).

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

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