Java 被测单元:Impl还是接口?

Java 被测单元:Impl还是接口?,java,unit-testing,dependency-injection,mockito,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 vo

假设我有接口和实现它的实现类,我想为此编写单元测试。我应该测试什么接口或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;
    }
}
所以,我有HelloInterface和HelloInterfaceImpl来实现它。什么是测试接口或Impl下的单元

我想应该是他的脸。请参考下面的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);

    }
 }
主线实际上是我注释掉的

((HelloInterfaceImpl)hi).setTarget(target)

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

编辑
我想我应该一直关注界面。在我看来,
setTarget()
也不是impl类的“契约”的一部分,它为sally提供依赖注入。我认为从测试的角度来看,Impl类的任何公共方法都应该被视为私有的。但这并不意味着我忽略了实现细节

另见


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

我会测试接口

我认为错误在于以这样一种方式编写实现,即硬连接到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");
    }
}
以下是测试:

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

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

如果您有一个关键接口,并且希望确保每个实现都正确地遵循它,那么您可以编写一个测试套件,该套件将重点放在该接口上,并要求传入一个实例(与任何实现类型无关)


是的,在PrintStream中使用Mockito可能会更容易,但不可能总是像您在这个特定示例中所做的那样避免使用mock对象。

我想说,这取决于实现以及它在接口契约之外的作用。许多实现只实现接口中提供的功能,而在其他实现中,接口只是类功能的一小部分。它可以实现多个接口

最终,您将测试实现

在你定义的一个简单的例子中,我说六个一个半打另一个。将测试用例写入接口或实现,只要它充分测试了实现,结果是相同的


举一个不同的例子,我有一个类,通过装饰real读写器来收集通信通道上的统计数据。我的类现在可以实现这些接口,但它也收集统计数据,这与两个契约都无关。我当然仍然可以基于这些接口编写测试,但它不会完全测试这个类。

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

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

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

让我们以JDK为例。您有接口
List
和两个实现:
ArrayList
LinkedList
。另外,
LinkedList
实现了
Deque
接口。如果您为
列表
接口编写测试,您将涵盖哪些内容?数组还是链表?更重要的是,如果是
LinkedList
,您会选择测试什么接口<代码>数据
列表
?正如您所看到的,当您测试实现时,您不会遇到这样的问题


就我个人而言,在单元测试中将接口转换为实现是出现问题的明显标志;)

可以将其视为我的接口的另一个实现。:-)从依赖注入的角度来看,这实际上是一个争论,什么是更好的构造函数注入还是setter注入。这不是我要问的问题。但这并不意味着我忽略了实现细节。你不能-没有其他东西需要测试。@duffymo-1。我认为你给出了一个很好的重构形式的例子,但没有真正回答这个问题,因为你只是隐藏了op的问题。我猜你不是在争论每个实现只会暴露除接口之外的setter,如果我是对的,那么op的问题仍然存在。不管怎样-你的答案在哪里?我认为我应该始终关注接口。在我看来,
setTarget()
也不是impl类的“契约”的一部分,它为depende服务
((HelloInterfaceImpl)hi).setTarget(target);