假设我有接口和实现它的实现类,我想为此编写单元测试,我应该测试接口还是实现?
下面是一个示例:
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);
}
}
型
主线实际上是我注解掉的一条。
第一个月
方法setTarget()
不是我的公共接口的一部分,所以我不想 * 意外地 * 调用它。如果我真的想调用它,我应该花点时间考虑一下。例如,它帮助我发现我真正想做的是依赖注入。它为我打开了整个世界的新机会。我可以使用一些现有的依赖注入机制(例如Spring的),我可以自己模拟它,就像我在代码中实际做的那样,或者采取完全不同的方法。仔细看看,PrintSort的准备并不那么容易,也许我应该使用mock对象来代替?
编辑:我认为我应该 * 始终 * 关注接口。从我的Angular 来看,setTarget()
也不是impl类的“合约”的一部分,它为依赖注入提供了sally。我认为从测试的Angular 来看,Impl类的任何公共方法都应该被视为私有的。但这并不意味着我忽略了实现细节。
参见Should Private/Protected methods be under unit test?
EDIT-2在多个实现\多个接口的情况下,我会测试所有的实现,但是当我在setUp()
方法中声明一个变量时,我肯定会使用interface。
5条答案
按热度按时间tyg4sfes1#
实现是需要测试的单元,当然也是你正在示例化的,包含了程序/业务逻辑。
如果你有一个关键的接口,并且你想确保每个实现都正确地遵循它,那么你可以编写一个测试套件,专注于接口,并要求传入一个示例(与任何实现类型无关)。
是的,对于PrintStream使用Mockito可能会更容易,可能并不总是可以避免使用mock对象,就像您在这个特定示例中所做的那样。
pengsaosao2#
我总是测试实现--一个类可以实现多个接口,一个接口也可以由多个类实现--每个类都应该被测试所覆盖。
在单元测试中调用setter的要求(将接口转换为实现):
字符串
这不是合约的一部分,但这是使实现工作的重要部分,应该进行适当的测试。
让我们以JDK为例。您有接口
List
和两个实现:ArrayList
和LinkedList
。另外,LinkedList
实现了Deque
接口。如果您为List
接口编写测试,您会覆盖什么?数组还是链表?更重要的是,如果是LinkedList
,您会选择测试什么接口?Deque
或List
?正如您所看到的,当您测试实现时,您不会遇到这样的问题。就我个人而言,在单元测试中将接口转换为实现是出问题的明显迹象;)
xxhby3vn3#
我会测试接口。
我认为错误在于以这样一种方式编写实现,即它是硬连接的,可以写入System.out;你没有办法用另一个PrintStream重写。我会使用构造函数而不是setter。这样就不需要mock或强制转换。
这是一个简单的例子。我想象一个更复杂的例子会有一个工厂来创建不同的,更复杂的接口实现。希望你不会设计成这样,你会被困住。
在测试中坚持使用接口也会使模拟变得容易得多。
字符串
测试如下:
型
uqdfh47h4#
我会说这取决于实现和它在接口契约之外的作用。许多实现只实现接口中提供的功能,在其他情况下,接口只是类功能的一小部分。它可能实现多个接口。
最后,您将测试实现。
在一个像你定义的简单案例中,我说的是半斤八两。把你的测试用例写在接口或实现上,只要它充分测试了 * 实现 *,结果是一样的。
举一个不同的例子,我有一个类,它通过装饰真实的读取器和写入器来收集通信信道上的统计数据。我的类现在可以实现这些接口,但它也收集统计数据,这与任何一个合约都没有关系。我当然仍然可以基于这些接口编写测试,但它不会完全测试这个类。
7gs2gvoe5#
我喜欢将接口标注为完整的合约(添加前置条件和后置条件标注),它以半正式的方式指定了实现接口的任何类的需求。从那里你可以生成或编写合约遵从性测试。任何实现了许多接口/合约的类都必须通过它实现的所有合约的测试。
其优点是测试绑定到需求(接口升级为合约),并且任何声称实现合约的新类都要测试它是否实现。