java 受试单元:植入物或接口?

czq61nw1  于 12个月前  发布在  Java
关注(0)|答案(5)|浏览(78)

假设我有接口和实现它的实现类,我想为此编写单元测试,我应该测试接口还是实现?
下面是一个示例:

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。

tyg4sfes

tyg4sfes1#

实现是需要测试的单元,当然也是你正在示例化的,包含了程序/业务逻辑。
如果你有一个关键的接口,并且你想确保每个实现都正确地遵循它,那么你可以编写一个测试套件,专注于接口,并要求传入一个示例(与任何实现类型无关)。
是的,对于PrintStream使用Mockito可能会更容易,可能并不总是可以避免使用mock对象,就像您在这个特定示例中所做的那样。

pengsaosao

pengsaosao2#

我总是测试实现--一个类可以实现多个接口,一个接口也可以由多个类实现--每个类都应该被测试所覆盖。
在单元测试中调用setter的要求(将接口转换为实现):

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

字符串
这不是合约的一部分,但这是使实现工作的重要部分,应该进行适当的测试。
让我们以JDK为例。您有接口List和两个实现:ArrayListLinkedList。另外,LinkedList实现了Deque接口。如果您为List接口编写测试,您会覆盖什么?数组还是链表?更重要的是,如果是LinkedList,您会选择测试什么接口?DequeList?正如您所看到的,当您测试实现时,您不会遇到这样的问题。
就我个人而言,在单元测试中将接口转换为实现是出问题的明显迹象;)

xxhby3vn

xxhby3vn3#

我会测试接口。
我认为错误在于以这样一种方式编写实现,即它是硬连接的,可以写入System.out;你没有办法用另一个PrintStream重写。我会使用构造函数而不是setter。这样就不需要mock或强制转换。
这是一个简单的例子。我想象一个更复杂的例子会有一个工厂来创建不同的,更复杂的接口实现。希望你不会设计成这样,你会被困住。
在测试中坚持使用接口也会使模拟变得容易得多。

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

uqdfh47h

uqdfh47h4#

我会说这取决于实现和它在接口契约之外的作用。许多实现只实现接口中提供的功能,在其他情况下,接口只是类功能的一小部分。它可能实现多个接口。
最后,您将测试实现。
在一个像你定义的简单案例中,我说的是半斤八两。把你的测试用例写在接口或实现上,只要它充分测试了 * 实现 *,结果是一样的。
举一个不同的例子,我有一个类,它通过装饰真实的读取器和写入器来收集通信信道上的统计数据。我的类现在可以实现这些接口,但它也收集统计数据,这与任何一个合约都没有关系。我当然仍然可以基于这些接口编写测试,但它不会完全测试这个类。

7gs2gvoe

7gs2gvoe5#

我喜欢将接口标注为完整的合约(添加前置条件和后置条件标注),它以半正式的方式指定了实现接口的任何类的需求。从那里你可以生成或编写合约遵从性测试。任何实现了许多接口/合约的类都必须通过它实现的所有合约的测试。
其优点是测试绑定到需求(接口升级为合约),并且任何声称实现合约的新类都要测试它是否实现。

相关问题