java - method - spring mockito




Единица измерения: Impl или интерфейс? (3)

Предположим, что у меня есть интерфейс и класс реализации, который его реализует, и я хочу написать unit-test для этого. Что я должен проверить интерфейс или 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() не является частью моего открытого интерфейса, поэтому я не хочу случайно его называть. Если я действительно хочу это назвать, я должен подумать об этом. Это помогает мне, например, обнаружить, что я действительно пытаюсь сделать инъекцию зависимости. Это открывает для меня весь мир новых возможностей. Я могу использовать какой-то существующий механизм инъекции зависимостей (например, Spring), я могу имитировать его сам, как я это делал в своем коде, или использовать совершенно другой подход. Присмотритесь, подготовка PrintSream была не такой простой, может быть, я должен использовать макет?

EDIT : Я думаю, я всегда должен сосредоточиться на интерфейсе. С моей точки зрения setTarget() не является частью «контракта» класса impl, но он служит для активации инъекций. Я думаю, что любой публичный метод класса Impl следует считать приватным с точки зрения тестирования. Это не значит, что я игнорирую детали реализации.

См. Также Если частные / защищенные методы должны проходить единичный тест?

EDIT-2 В случае нескольких реализаций \ нескольких интерфейсов я бы тестировал все реализации, но когда я объявляю переменную в моем setUp() я бы определенно использовал интерфейс.


Реализация - это единица, которая должна быть протестирована. Это, конечно, то, что вы создаете, и что содержит программную / бизнес-логику.

Если у вас был критический интерфейс, и вы хотели удостовериться, что каждая реализация должным образом соответствует ему, вы можете написать набор тестов, который фокусируется на интерфейсе и требует, чтобы экземпляр был передан (агностик любого типа реализации).

Да, вероятно, было бы проще использовать Mockito для PrintStream, не всегда можно избежать использования макетного объекта, как в этом конкретном примере.


Я бы проверил интерфейс.

Я думаю, что ошибка заключалась в том, что запись была выполнена таким образом, что было сложно подключиться к System.out; вы не смогли переопределить другой PrintStream. Я бы использовал конструктор вместо сеттера. Там нет необходимости в макете или кастинге.

Это простой случай. Я бы предположил, что более сложный будет иметь фабрику для создания различных, более сложных реализаций интерфейса. Надеюсь, вы не разработали бы это так, чтобы вы были в коробке.

Приклеивание к интерфейсу в ваших тестах делает насмешливым намного проще.

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

Я всегда тестирую реализации - один класс может реализовывать несколько интерфейсов, а также один интерфейс может быть реализован несколькими классами - каждый из них должен быть охвачен испытаниями.

Требование вызова setter в модульном тесте (литье интерфейса для реализации):

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

означает, что вы действительно проверяете реализацию. Это не часть контракта, но это важная часть, которая заставляет реализацию работать, и ее следует проверять правильно.

Возьмем пример из JDK. У вас есть интерфейс List и две реализации: ArrayList и LinkedList . Дополнительно LinkedList реализует интерфейс Deque . Если вы напишете тест для интерфейса List что бы вы написали? Массив или связанный список? Что еще в случае LinkedList , какой интерфейс вы бы выбрали для тестирования? Deque или List ? Как вы видите, когда вы тестируете реализации, у вас нет таких проблем.

Для меня лично листинг интерфейса для реализации в модульном тесте является очевидным признаком того, что что-то идет не так;)







mockito