被测单元:Impl还是接口?

ale*_*ail 31 java unit-testing dependency-injection mockito

假设我有实现它的接口和实现类,我想为此编写单元测试.我应该测试什么接口或Impl?

这是一个例子:

public interface HelloInterface {
    public void sayHello();
}


public class HelloInterfaceImpl implements HelloInterface {
    private PrintStream target = System.out;


    @Override
    public void sayHello() {
        target.print("Hello World");

    }

    public void setTarget(PrintStream target){
        this.target = target;
    }
}
Run Code Online (Sandbox Code Playgroud)

所以,我有HelloInterface和HelloInterfaceImpl来实现它.什么是被测单元接口或Impl?

我认为它应该是HelloInterface.考虑下面的JUnit测试草图:

public class HelloInterfaceTest {
    private HelloInterface hi;

    @Before
    public void setUp() {
        hi = new HelloInterfaceImpl();
    }

    @Test
    public void testDefaultBehaviourEndsNormally() {
        hi.sayHello();
        // no NullPointerException here
    }

    @Test
    public void testCheckHelloWorld() throws Exception {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        PrintStream target = new PrintStream(out);
        PrivilegedAccessor.setValue(hi, "target", target);
        //You can use ReflectionTestUtils in place of PrivilegedAccessor
        //really it is DI 
        //((HelloInterfaceImpl)hi).setTarget(target);
        hi.sayHello();
        String result = out.toString();
        assertEquals("Hello World", result);

    }
 }
Run Code Online (Sandbox Code Playgroud)

主线实际上是我评论过的一条.

((HelloInterfaceImpl)hi).setTarget(target);

方法setTarget()不是我的公共接口的一部分,所以我不想意外地调用它.如果我真的想打电话给我,我应该花点时间考虑一下.例如,它帮助我发现我真正想做的是依赖注入.它为我提供了全新的机会.我可以使用一些现有的依赖注入机制(例如Spring),我可以自己模拟它,就像我在代码中实际做的那样,或采取完全不同的方法.仔细看看,PrintSream的准备工作并不那么容易,也许我应该使用模拟对象代替?

编辑:我想我应该始终专注于界面.从我的观点来看setTarget(),它既不是impl类的"契约"的一部分,也不依赖于依赖注入.我认为从测试的角度来看,Impl类的任何公共方法都应该被认为是私有的.但这并不意味着我忽略了实现细节.

另请参见专用/受保护方法是否应进行单元测试?

EDIT-2在多个实现\多个接口的情况下,我会测试所有的实现,但是当我在我的setUp()方法中声明一个变量时,我肯定会使用接口.

Tim*_*der 18

实现是需要测试的单元.这当然是您实例化的内容以及包含程序/业务逻辑的内容.

如果你有一个关键接口,并且你想确保每个实现都正确地遵守它,那么你可以编写一个专注于接口的测试套件,并要求传入一个实例(不知道任何实现类型).

是的,将Mockito用于PrintStream可能会更容易,但可能无法像在此特定示例中那样使用模拟对象.

  • 我认为我应该始终_关注_界面。从我的角度来看,`setTarget()`也不是 impl 类的“契约”的一部分,它为依赖注入提供了帮助。我认为从测试的角度来看,Impl 类的任何公共方法都应该被视为私有方法。 (2认同)

duf*_*ymo 7

我会测试界面.

我认为错误在于以这样的方式编写实现,即写入System.out很难.你没有办法用另一个PrintStream覆盖你自己.我会使用构造函数而不是setter.这种方式不需要模拟或铸造.

这是一个简单的案例.我想,一个更复杂的工厂会有一个工厂来创建不同的,更复杂的接口实现.希望你不会以这样的方式设计它,以便你被装箱.

坚持测试中的界面也使得模拟变得更加容易.

public class HelloInterfaceImpl implements HelloInterface {

    private PrintStream target;

    public HelloInterfaceImpl() {
        this(System.out);
    }

    public HelloInterfaceImpl(PrintStream ps) { 
       this.target = ps;
    }

    @Override
    public void sayHello() {
        target.print("Hello World");
    }
}
Run Code Online (Sandbox Code Playgroud)

这是测试:

public class HelloInterfaceTest {

    @Test
    public void testDefaultBehaviourEndsNormally() {
        HelloInterface hi = new HelloInterfaceImpl();    
        hi.sayHello();
    }

    @Test
    public void testCheckHelloWorld() throws Exception {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        PrintStream target = new PrintStream(out);
        HelloInterface hi = new HelloInterfaceImpl(target);    
        hi.sayHello();
        String result = out.toString();
        assertEquals("Hello World", result);
    }
}
Run Code Online (Sandbox Code Playgroud)


omn*_*nom 6

我总是测试实现 - 一个类可以实现几个接口,一个接口可以由几个类实现 - 每个类都应该由测试覆盖.

在单元测试中调用setter的要求(将接口强制转换为实现):

((HelloInterfaceImpl)hi).setTarget(target);
Run Code Online (Sandbox Code Playgroud)

意味着您实际测试实现.这不是合同的一部分,但这是使实施工作的重要部分,应该进行适当的测试.

我们来自JDK的一个例子.你有接口List和两个实现:ArrayListLinkedList.另外LinkedList实现Deque接口.如果您为List界面编写测试,您将涵盖哪些内容?数组或链表?更重要的是LinkedList,你会选择哪种界面进行测试?Deque还是List?如您所见,当您测试实现时,您没有遇到此类问题.

对我来说,个人来说,在单元测试中实现接口实现是出现问题的明显迹象;)

  • 这里我们只用一个简单的setter来考虑例子.但是如果你需要在setUp()中重新调用bean的一个生命周期方法(再次强制转换)呢?如果你必须测试#equals方法怎么办?再次 - 您将不得不使用特定类型(具体实现).在我提到的例子中,你需要多次施放.我认为这是最好的解释,明确地测试实现是更好的方法. (2认同)