被测单元:Impl还是接口?

假设我有实现它的接口和实现类,我想为此编写unit testing。 我应该测试什么接口或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?

我认为它应该是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); } } 

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

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

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

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

另请参见私有/受保护方法是否应进行unit testing?

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

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

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

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

我会测试界面。

我认为错误在于以这样的方式编写实现,即写入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); } } 

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

在unit testing中调用setter的要求(将接口转换为实现):

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

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

我们来自JDK的一个例子。 您有接口List和两个实现: ArrayListLinkedList 。 另外LinkedList实现了Deque接口。 如果你为List接口编写测试,你会覆盖什么? 数组或链表? 在LinkedList情况下,您会选择测试哪个界面? Deque还是List ? 如您所见,当您测试实现时,您没有遇到此类问题。

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

我会说这取决于实现以及它在接口合同之外的作用。 许多实现仅实现接口中提供的function,在其他实现中,接口仅是类function的一小部分。 它可以实现多个接口。

最终,您正在测试实施。

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

举一个不同的例子,我将有一个类通过装饰真正的读者和作者来收集通信渠道的统计数据。 我的类现在可以实现这些接口,但它也收集统计信息,这与任何一个合同无关。 我当然可以根据这些接口编写测试,但它不会完全测试这个类。