модульное тестирование в контексте запутанной пружины

Учитывая, что устаревшее приложение имеет 1500 spring.xmls. Я хочу написать модульный тест для службы. Я глубоко в аду зависимости. Я должен принять приложение как есть, выхода нет.

Итак, мы используем spring-3.something и mockito-1.9, и мне нужен хороший способ протестировать сервис. В более новом коде активно используется аннотация @Autowired.

Косвенно сервис использует ~25 хелперов (фабричные методы и т.д.), которые я на самом деле хочу использовать в тесте, и ~25 объектов, которые мне не интересны для этого теста.

В настоящее время я пытаюсь настроить контекст так, как описано выше, но меня смущают эффекты @Mock, @InjectMocks, @Autowired.

Мой тест приведен ниже. Мне нужна помощь, чтобы настроить его правильно.

Вопросы:

  • каков фактический эффект @InjectMocks?
  • как я могу решить (технически), какие автосвязанные bean-компоненты действительно используются, а какие заменены макетами?
  • Я знаю, что злоупотребляю имитациями для получения подделок. Есть ли более простой способ получить подделки в однострочниках?
  • *Обратите внимание, что я хочу понять это, поскольку у меня есть множество таких сервисов... *

Вот мой образец:

@ContextConfiguration(locations = {
    "classpath:/some/path/MainTestConfig.spring.xml"
})
@RunWith(SpringJUnit4ClassRunner.class)
public class SampleTest {

    // ***            Uninteresting Dependencies to be mocked        *** //
    @Mock Mock1 mock1;
    @Mock Mock2 mock2;

    /** Service under test */
    @Autowired
    SomeService service;

    // ***            Tightly coupled helpers to be used              *** //
    @Autowired Helper1 helper1
    @Autowired Helper2 helpr2

    @Before
    public void setup() {
        MockitoAnnotations.initMocks(SampleTest.class);
    }

    @Test
    public void testSample() {

        // prepare dummy context
        SomeContext context = new Context();

        // define expected result
        int expectedValue = 42;

        //execute method under test, record result
        Result actualResult = service.execute(context);

        //make assertions on result
        assertTrue(actualResult.getSomething()==expectedValue);
    }

}

person Bastl    schedule 15.04.2013    source источник
comment
Я не понимаю, почему у вас есть mock1 и mock2, которые кажутся неуместными в вашем тесте, поскольку они не имеют значения. Плюс помощники, похоже, не используются, проверено в тесте. Если все подключено к пружине, зачем вам это нужно. Я считаю, что вместо этого вы должны издеваться над тесно связанными помощниками и проверять взаимодействие. Кроме того, в конечном итоге вы можете написать этот тест как большой тест функциональности / функций, то есть не модульный тест или интеграционный тест, поэтому он будет помещен в какой-то другой пакет, чтобы показать разницу вашим коллегам.   -  person Brice    schedule 15.04.2013
comment
Кстати, вы пробовали конфигурацию аннотаций, доступную весной 3? Это может быть полезно, если вам нужно внедрить только несколько зависимостей, и это может работать и для макетов (нет необходимости в springockito, если добавление другой зависимости от maven вызывает беспокойство).   -  person Brice    schedule 15.04.2013


Ответы (2)


Я хотел опубликовать это как отдельный ответ, так как это должно лучше ответить на ваш фактический вопрос относительно @Mock, @Autowired и @InjectMocks.

@Mock: это отмечает поле, которое должно быть создано как макет (с использованием mock(MyClass.class)) при вызове MockitoAnnotations.initMocks(this).

@Autowired: Помечает поле, которому Spring должен назначить bean-компонент, реализующий класс/интерфейс.

@InjectMocks: Отмечает поле, которое должно быть СОЗДАНО Mockito при вызове MockitoAnnoations.initMocks(this). Он создает экземпляр класса и вводит в этот экземпляр поля с аннотациями @Mock. (см. исправление к этому утверждению в комментариях).

Анализ:

@InjectMocks не совместим с контекстами Spring и @Autowired, потому что InjectMock создает новый экземпляр класса, который не использует экземпляр Spring.

Чтобы сделать то, что вы ищете, вам нужно использовать Springockito. (отложенное обновление) Springockito позволит вам вводить макеты в ваш контекст Spring и, таким образом, использовать эти макеты в качестве Autowired кандидатов. Это позволяет использовать Mocks и Spys. Использование ReplaceWithMock и Autowired в одном и том же поле в вашем тесте является общей практикой (как показано в примере на вики).

person John B    schedule 15.04.2013
comment
Это утверждение неверно: ...InjectMock создает новый экземпляр класса, в котором не используется экземпляр Spring. Фактическое поведение Mockito заключается в том, что он никогда не создаст экземпляр, если поле уже имеет его во время инъекции mockito. Хотя я признаю, что в документации нет ясности по этому поводу: InjectMocks Javadoc . Таким образом, можно внедрить макеты в управляемый bean-компонент spring, НО я бы не рекомендовал это делать, так как это сильно усложняет настройку теста. - person Brice; 15.04.2013
comment
@Brice, значит, вы говорите, что если вы поместите @Autowired и @InjectMocks (для получения Spring bean-компонента требуется Autowired) в одно и то же поле в тесте, Spring bean-компонент будет использоваться, и он будет обновлен путем внедрения в него макетов из контрольная работа? - person John B; 15.04.2013
comment
Однако обратите внимание, что управляемый bean-компонент spring будет обновляться с помощью макетов на всю его жизнь в контексте spring, поэтому я не рекомендую его. Использование трюка с грязным контекстом может работать, но это намного медленнее, особенно с огромными контекстами Spring, поскольку Spring будет загружать контекст для каждого метода тестирования. - person Brice; 15.04.2013

Первый вопрос: вам НУЖНО загружать контекст весны для этого теста? Обычно, когда у меня есть проекты Spring, у меня есть один UNIT-тест на класс, который НЕ загружает файл контекста. У меня будет другой тест CONTEXT для загрузки файла контекста и проверки правильности его загрузки. Если вы выполняете настоящее модульное тестирование, я бы посоветовал не загружать контекст. Если тестируемый класс использует @Autowired для назначения зависимостей (и поэтому не имеет сеттеров), используйте Spring ReflectionTestUtils для назначения этих полей.

person John B    schedule 15.04.2013
comment
хорошо, модуль, который я тестирую, — это сервис + его помощники. Для этого мне нужно настроить некоторый контекст, состоящий из помощников и манекенов, которые ничего не делают. Настройка этого контекста вручную невозможна для 50 классов, поэтому я хотел бы повторно использовать существующую конфигурацию Spring, где это возможно, и внедрять макеты, где это возможно. Кажется, я еще не получил InjectMocks... - person Bastl; 15.04.2013
comment
Кстати: что такое настоящее модульное тестирование? что такое единица по твоему? У меня есть сомнения, заменяя единицу классом или методом. Мое подразделение это служба + помощники. Все, что за этой границей, должно быть осмеяно/подделано. - person Bastl; 15.04.2013
comment
Настоящий модульный тест - это когда ваш тест касается только ОДНОГО класса (модуля). Все остальные классы/зависимости издеваются. Единственные конкретные классы, которые я разрешаю, кроме тестируемого класса, — это объекты передачи данных (DTO), которые просто являются держателями данных (полями, геттерами, сеттерами). - person John B; 15.04.2013
comment
Если вы тестируете сервис + его помощники, я бы не стал считать это настоящим модульным тестом. Не сказать, что это плохой тест, просто он называется по-другому. В нашем проекте мы называем это тестом функций (полностью произвольное название, но отличается от модульного теста). Как правило, их сложнее настроить, и иногда они используют ограниченный контекст Spring. Однако у них должно быть меньше тестов и не все модульные тесты для каждого класса. Целью этих тестов должно быть обеспечение правильного взаимодействия классов. - person John B; 15.04.2013
comment
хм, кажется, мое образование в области тестирования использовало устаревшее название: В The Art of Software Testing, Myers, 1979! определение такое: Модульное тестирование (или модульное тестирование) процесс тестирования отдельных подпрограмм, подпрограмм или процедур в программе. Это мое понимание, но оно, кажется, противоречит текущему (основанному на ООП) пониманию (j) модульного тестирования. Действительно, я ищу подход к тестированию на более высоком уровне (функция, но еще не полностью интегрированное приложение) вместо слишком тонких тестов на уровне класса или метода (как я уже сказал, я живу в устаревшем контексте....) - person Bastl; 15.04.2013
comment
И просто для ясности: нет ничего плохого в использовании JUnit для создания этих тестов более высокого уровня. Просто помогает в общении называть их как-то иначе, чем модульные тесты. WRT ваш вопрос, вы находитесь в затруднительном положении, поскольку вы не можете изолировать классы в отдельные файлы Spring. Однако не могли бы вы загрузить только необходимые файлы Spring, а не корневой файл, который загружает все приложение? - person John B; 15.04.2013
comment
Я бы не определил, что настоящая единица всегда является одним классом. Предположим, вы начинаете реализовывать функцию в одном классе, но в какой-то момент она становится слишком большой или сложной. Затем вы начинаете извлекать из него вспомогательные классы, делая их приватными, поскольку они являются просто деталями реализации. Должны ли эти новые классы рассматриваться как отдельные модули с собственными модульными тестами? Я так не думаю. Я бы предпочел продолжать рассматривать логическую единицу так же, как и раньше, даже если теперь она включает в себя несколько классов. - person Rogério; 16.04.2013
comment
Я хотел сказать, что это предполагает, что когда большинство людей слышат модульный тест, они интерпретируют это как тест одного класса со всеми издевательствами над зависимостями. Мы также используем JUnit для тестирования этих функций, но мы называем их тестами функций. Семантика, я знаю. - person John B; 16.04.2013