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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/10937763/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-31 03:09:40  来源:igfitidea点击:

Unit under test: Impl or Interface?

javaunit-testingdependency-injectionmockito

提问by alexsmail

Suppose I have interface and implementation class that implements it and I want to write unit-test for this. What should I test interface or Impl?

假设我有实现它的接口和实现类,我想为此编写单元测试。我应该测试什么接口或Impl?

Here is an example:

下面是一个例子:

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

So, I have HelloInterface and HelloInterfaceImpl that implements it. What is unit-under-test interface or Impl?

所以,我有 HelloInterface 和 HelloInterfaceImpl 来实现它。什么是被测单元接口或 Impl?

I think it should be HelloInterface. Consider following sketch of JUnit test:

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

    }
 }

The main line is actually one that I commented out.

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

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

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

Method setTarget()is not part of my public interface, so I don't want to accidentallycall it. If I really want to call it, I should take a moment and think about it. It helps me, for example, to discover that what I'm really trying to do is dependency injection. It opens for me the whole world of new opportunities. I can use some existing dependency injection mechanism (Spring's, for example), I can simulate it myself as I actually did in my code or to take totally different approach. Take a closer look, preparation of PrintSream wasn't that easy, maybe I should use mock object instead?

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

EDIT: I think I should alwaysfocus on the interface. From my point of view setTarget()is not part of the "contract" of the impl class neither, it serves sally for dependency injection. I think any public method of Impl class should be considered as private from the testing perspective. It doesn't mean that I ignore the implementation details, though.

编辑:我认为我应该始终关注界面。从我的角度来看setTarget(),它也不是 impl 类的“合同”的一部分,它为依赖注入服务。我认为从测试的角度来看,Impl 类的任何公共方法都应该被视为私有方法。不过,这并不意味着我忽略了实现细节。

See also Should Private/Protected methods be under unit test?

另请参阅私有/受保护方法是否应进行单元测试?

EDIT-2In the case of multiple implementations\multiple interfaces, I would test all of the implementations, but when I declare a variable in my setUp()method I would definitely use interface.

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

采纳答案by Tim Bender

The implementation is the unit that needs to be tested. That is of course what you are instantiating and what contains the program/business logic.

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

If you had a critical interface and you wanted to make sure every implementation adhered to it properly, then you may write a test suite that focuses on the interface and requires an instance be passed in (agnostic of any implementation type).

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

Yes, it would probably be easier to use Mockito for PrintStream, it may not always be possible to avoid using a mock object like you did in this specific example.

是的,将 Mockito 用于 PrintStream 可能会更容易,但并非总是可以像在此特定示例中那样避免使用模拟对象。

回答by duffymo

I would test to the interface.

我会测试接口。

I think the mistake was writing the implemenation in such a way that it was hard-wired to write to System.out; you gave yourself no way to override with another PrintStream. I would have used a constructor instead of a setter. There's no need for a mock or casting that way.

我认为错误在于编写实现的方式是硬连接写入 System.out;你没有办法用另一个 PrintStream 覆盖。我会使用构造函数而不是 setter。没有必要以这种方式进行模拟或铸造。

This is a simple case. I'd imagine that a more complex one would have a factory for creating different, more complex implementations of an interface. Hopefully you wouldn't design that in such a way that you'd be boxed in.

这是一个简单的案例。我想象一个更复杂的人会有一个工厂来创建一个不同的、更复杂的接口实现。希望你不会以这样的方式设计它,你会被装箱。

Sticking to the interface in your tests makes mocking a lot easier, too.

在你的测试中坚持使用界面也会让模拟变得更容易。

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

Here's the test:

这是测试:

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

回答by omnomnom

I always test implementations - one class can implement several interfaces and also one interface can be implemented by several classes - each of them should be covered by tests.

我总是测试实现——一个类可以实现多个接口,一个接口也可以由多个类实现——每个类都应该被测试覆盖。

The requirement of invoking setter in the unit test (casting the interface to the implementation):

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

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

means that you actually test the implementation. It's not the part of the contract, but this is the important part making the implementation to work and should be tested properly.

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

Let's take an example from JDK. You have interface Listand two implementations: ArrayListand LinkedList. Additionally LinkedListimplements Dequeinterface. If you write test for Listinterface what would you cover? Array or linked list? What's more in case of LinkedList, what interface would you choose to test? Dequeor List? As you see, when you test implementations you don't have such problems.

让我们以JDK为例。您有接口List和两个实现:ArrayListLinkedList. 另外LinkedList实现Deque接口。如果你为List接口编写测试,你会覆盖什么?数组还是链表?更重要的是LinkedList,您会选择测试什么接口?Deque或者List?如您所见,当您测试实现时,您不会遇到此类问题。

For me, personally, casting interface to implementation in the unit test is obvious sign that something is going wrong ;)

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

回答by Robin

I would say it depends on the implementation and what it does beyond the contract of the interface. Many implementations only implement the functionality provided in the interface, in others, the interface is only a small portion of the classes functionality. It may implement multiple interfaces.

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

Ultimately you are testing the implementation.

最终,您正在测试实现。

In a simple case like you have defined, I say six of one and half a dozen of the other. Write your test cases to the interface or the implementation, as long as it tests the implementationsufficiently, the results are the same.

在您定义的一个简单案例中,我说一个是六个,另一个是六个。把你的测试用例写到接口或者实现上,只要对实现进行充分的测试,结果都是一样的。

Take a different example where I would have a class that collects statistics on a communication channel by decorating the realreader and writer. My class may now implement those interfaces, but it also collects statistics, which has nothing to do with either contract. I could certainly still write tests based on those interfaces, but it will not fully test this class.

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