Можно ли создать экземпляр класса для тестирования без вызова конструктора? - PullRequest
0 голосов
/ 01 марта 2019

Можно ли создать экземпляр класса с помощью сложного конструктора без насмешек или вызова его конструктора?Это было бы полезно, потому что класс модульного теста не должен изменяться каждый раз, когда в службу добавляется новая зависимость.

Пересечение службы решает проблему путем создания новой реализации класса с пустым методом.переопределяет, но это не пример реализации.Это проблема, потому что каждый раз, когда вызывается метод в mocked-сервисе, Mockito нужно сказать, чтобы он вызывал реальный метод.Вместо этого предпочтительнее было бы использовать реальную реализацию службы.

Например:

class ComplexService {
    private Service service1;
    private Service service2;
    private Service service3;
    private Service service4;
    private Service service5;

    ComplexConstructor(Service service1, Service service2, Service service3, Service service4, Service service5) {
        this.service1 = service1;
        this.service2 = service2;
        this.service3 = service3;
        this.service4 = service4;
        this.service5 = service5;
    }

    boolean methodToTest() {
        return service1.get();
    }
}

В классе модульного теста возможно создание экземпляра реализации без вызоваего конструктор?

public class ComplexConostructorTest {
    private ComplexConstructor complexConstructor;
    private Service serviceMock;

    @Before
    public void init() {
        /*
         Somehow create implementation of complexConstructor
         without calling constructor

         . . .
          */

        // Mock dependency
        ReflectionTestUtils.setField(complexConstructor, 
                "service1", 
                serviceMock = Mockito.mock(Service.class));
    }

    @Test
    public void service1Test() {
        when(serviceMock.get())
                .thenReturn(true);

        assertTrue(complexConstructor.methodToTest());
    }
}

Редактировать

Можно использовать отражение, я надеялся, что в JUnit или Mockito был встроен способ для достижения того же самого.Вот как это сделать, используя отражение.

@Before
public void init() {
    Constructor<?> constructor = ComplexConstructor.class.getConstructors()[0];
    complexConstructor = (ComplexConstructor) constructor.newInstance(new Object[constructor.getParameterCount()]);

    // Mock dependency
    ReflectionTestUtils.setField(complexConstructor,
            "service1",
            serviceMock = Mockito.mock(Service.class));
}

Ответы [ 5 ]

0 голосов
/ 02 марта 2019

Похоже, вы смешиваете несколько терминов и понятий.Позвольте мне помочь вам лучше понять их.

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

Ваш класс имеет ряд зависимостей, которые предоставляются через конструктор.Если вы пишете модульный тест, ваша цель - только , чтобы протестировать этот зависимый класс, все зависимости должны быть проверены.Вот почему это называется unit testing.Это означает, что с каждой новой зависимостью вашего класса тест должен обновляться путем добавления нового макета и его имитированного поведения.

Mockito должен быть вызван для вызова реального метода.Вместо этого предпочтительнее использовать реальную реализацию сервиса.

Рассмотрите интеграционные тесты, в этом случае вы можете смоделировать только некоторое количество зависимостей, в то время как другие будут работать как задумано или «реально», пока вы не будете имитироватьих конечно.Однако, если вы хотите просто избежать поддержки тестов, тогда это неправильный подход.

Пожалуйста, не пытайтесь взломать ваш тестируемый класс из теста путем рефлексии.Это может привести к неверным результатам тестирования, пустой трате времени и общему разочарованию :) Библиотеки-насмешки, такие как PowerMock и JMockit, предоставляют любые виды хаков, например, те, которые вы пытались реализовать самостоятельно, и в целом они слишком мощные.

С великой силой приходит великая ответственность

0 голосов
/ 01 марта 2019

Вы можете создавать вспомогательные методы как часть вашего тестового кода, например, некоторые фабричные методы с описательными именами, которые создают объект.Например, make_default_ComplexService и т. П., Так же как это требуется вашими тестами.Затем тесты могут использовать эти методы, и если конструктор изменится, во многих случаях вам потребуется обновить только вспомогательные методы, а не все тесты.Этот подход достаточно универсален, чтобы также отделить ваш тест от радикальных изменений, таких как превращение подхода «конструктор с параметрами» в подход «конструктор без аргументов с большим количеством методов установки».

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

0 голосов
/ 01 марта 2019

Вы можете добавить конструктор без параметров в ваш класс и создать установщик для всех ваших полей.

class ComplexService {
    private Service service1;
    private Service service2;
    private Service service3;
    private Service service4;
    private Service service5;

    public ComplexService(){
      super();
    }
    ...

    public void setService1(Service service1){
      this.service1 = service1;
    }
    //for other fields too
    ...
}

В своем тесте вы называете его следующим образом:

myService = new ComplexService()
myService.setService1(service1);
...
0 голосов
/ 01 марта 2019

Вы можете, но, вероятно, не хотите:

ComplexConstructor partialMock =
    Mockito.mock(ComplexConstructor.class, CALLS_REAL_METHODS);

В этом "частичном макете" экземпляру не будут вызываться конструктор или инициализаторы полей, но все вызовытестируемая система будет вызывать реальное поведение класса.(Технически класс также будет иметь свои equals и hashCode переопределенные для целей Mockito, и класс будет сгенерированным подклассом ComplexConstructor вместо самого ComplexConstructor.)

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

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

0 голосов
/ 01 марта 2019

Насколько я знаю, это невозможно, но вы можете просто добавить более простой конструктор в свой класс и использовать его для тестирования.С другой стороны, если тест проверяет объект в состоянии, отличном от того, каким он будет в приложении, я не уверен, насколько хорошим будет такой тест.

...