Java 被测单元:Impl还是接口?
假设我有接口和实现它的实现类,我想为此编写单元测试。我应该测试什么接口或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
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);