Перейти к основному содержимому

Spring TestExecutionListener

· 5 мин. чтения

1. Обзор

Обычно мы используем аннотации JUnit, такие как @BeforeEach, @AfterEach, @BeforeAll и @AfterAll, для управления жизненным циклом тестов, но иногда этого недостаточно, особенно когда мы работаем с инфраструктурой Spring.

Вот где Spring TestExecutionListener пригодится.

В этом руководстве мы увидим, что предлагает TestExecutionListener , прослушиватели по умолчанию, предоставляемые Spring, и как реализовать собственный TestExecutionListener .

2. Интерфейс TestExecutionListener

Во-первых, давайте посетим интерфейс TestExecutionListener :

public interface TestExecutionListener {
default void beforeTestClass(TestContext testContext) throws Exception {};
default void prepareTestInstance(TestContext testContext) throws Exception {};
default void beforeTestMethod(TestContext testContext) throws Exception {};
default void afterTestMethod(TestContext testContext) throws Exception {};
default void afterTestClass(TestContext testContext) throws Exception {};
}

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

Этот объект TestContext содержит информацию о контексте Spring и целевом тестовом классе и методах. Эта информация может быть использована для изменения поведения тестов или расширения их функциональности.

Теперь давайте кратко рассмотрим каждый из этих методов:

  • afterTestClass — выполняет постобработку тестового класса после выполнения всех тестов в классе.
  • afterTestExecution — выполняет постобработку теста сразу после выполнения тестового метода в предоставленном тестовом контексте.
  • afterTestMethod — выполняет постобработку теста после выполнения обратных вызовов после жизненного цикла базовой тестовой среды.
  • beforeTestClass — предварительно обрабатывает тестовый класс перед выполнением всех тестов в классе.
  • beforeTestExecution — предварительно обрабатывает тест непосредственно перед выполнением тестового метода в предоставленном тестовом контексте.
  • beforeTestMethod - предварительно обрабатывает тест перед выполнением обратных вызовов перед жизненным циклом базовой тестовой среды.
  • prepareTestInstance - подготавливает тестовый экземпляр предоставленного тестового контекста.

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

3. Spring TestExecutionListeners по умолчанию

По умолчанию Spring предоставляет некоторые готовые реализации TestExecutionListener .

Давайте быстро рассмотрим каждый из них:

  • ServletTestExecutionListener — настраивает макеты Servlet API для WebApplicationContext.
  • DirtiesContextBeforeModesTestExecutionListener — обрабатывает аннотацию @DirtiesContext для режимов «до» .
  • DependencyInjectionTestExecutionListener — обеспечивает внедрение зависимостей для экземпляра теста.
  • DirtiesContextTestExecutionListener — обрабатывает аннотацию @DirtiesContext для режимов «после» .
  • TransactionalTestExecutionListener — обеспечивает выполнение транзакционного теста с семантикой отката по умолчанию.
  • SqlScriptsTestExecutionListener — запускает сценарии SQL, настроенные с помощью аннотации @Sql.

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

4. Использование пользовательского TestExecutionListener

Теперь давайте определим собственный TestExecutionListener :

public class CustomTestExecutionListener implements TestExecutionListener, Ordered {
private static final Logger logger = LoggerFactory.getLogger(CustomTestExecutionListener.class);

public void beforeTestClass(TestContext testContext) throws Exception {
logger.info("beforeTestClass : {}", testContext.getTestClass());
};

public void prepareTestInstance(TestContext testContext) throws Exception {
logger.info("prepareTestInstance : {}", testContext.getTestClass());
};

public void beforeTestMethod(TestContext testContext) throws Exception {
logger.info("beforeTestMethod : {}", testContext.getTestMethod());
};

public void afterTestMethod(TestContext testContext) throws Exception {
logger.info("afterTestMethod : {}", testContext.getTestMethod());
};

public void afterTestClass(TestContext testContext) throws Exception {
logger.info("afterTestClass : {}", testContext.getTestClass());
}

@Override
public int getOrder() {
return Integer.MAX_VALUE;
};
}

Для простоты все, что делает этот класс, — это регистрирует некоторую информацию о TestContext .

4.1. Регистрация пользовательского прослушивателя с помощью @TestExecutionListeners

Теперь давайте используем этот слушатель в нашем тестовом классе. Для этого мы зарегистрируем его с помощью аннотации @TestExecutionListeners :

@RunWith(SpringRunner.class)
@TestExecutionListeners(value = {
CustomTestExecutionListener.class,
DependencyInjectionTestExecutionListener.class
})
@ContextConfiguration(classes = AdditionService.class)
public class AdditionServiceUnitTest {
// ...
}

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

Если нам нужны какие-либо другие прослушиватели по умолчанию, мы должны указать каждый из них. Но мы также можем использовать свойство mergeMode аннотации:

@TestExecutionListeners(
value = { CustomTestExecutionListener.class },
mergeMode = MergeMode.MERGE_WITH_DEFAULTS)

Здесь MERGE_WITH_DEFAULTS указывает, что локально объявленные прослушиватели должны быть объединены с прослушивателями по умолчанию.

Теперь, когда мы запустим приведенный выше тест, прослушиватель будет регистрировать каждое полученное событие:

[main] INFO  o.s.t.c.s.DefaultTestContextBootstrapper - Using TestExecutionListeners: 
[com.foreach.testexecutionlisteners.CustomTestExecutionListener@38364841,
org.springframework.test.context.support.DependencyInjectionTestExecutionListener@28c4711c]
[main] INFO c.b.t.CustomTestExecutionListener - beforeTestClass :
class com.foreach.testexecutionlisteners.TestExecutionListenersWithoutMergeModeUnitTest
[main] INFO c.b.t.CustomTestExecutionListener - prepareTestInstance :
class com.foreach.testexecutionlisteners.TestExecutionListenersWithoutMergeModeUnitTest
[main] INFO o.s.c.s.GenericApplicationContext -
Refreshing org.springframework.context.support.GenericApplicationContext@7d68ef40: startup date [XXX];
root of context hierarchy
[main] INFO c.b.t.CustomTestExecutionListener - beforeTestMethod :
public void com.foreach.testexecutionlisteners.TestExecutionListenersWithoutMergeModeUnitTest
.whenValidNumbersPassed_thenReturnSum()
[main] INFO c.b.t.CustomTestExecutionListener - afterTestMethod :
public void com.foreach.testexecutionlisteners.TestExecutionListenersWithoutMergeModeUnitTest
.whenValidNumbersPassed_thenReturnSum()
[main] INFO c.b.t.CustomTestExecutionListener - afterTestClass :
class com.foreach.testexecutionlisteners.TestExecutionListenersWithoutMergeModeUnitTest

4.2. Автоматическое обнаружение реализаций TestExecutionListener по умолчанию

Использование @TestExecutionListener для регистрации слушателей подходит, если оно используется в ограниченном числе тестовых классов. Но добавление его ко всему набору тестов может стать громоздким.

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

Модуль spring-test объявляет всех основных слушателей по умолчанию в ключе org.springframework.test.context.TestExecutionListener в своем файле свойств META-INF/spring.factories . Точно так же мы можем зарегистрировать наш собственный прослушиватель, используя указанный выше ключ в нашем собственном файле свойств META-INF/spring.factories :

org.springframework.test.context.TestExecutionListener=\
com.foreach.testexecutionlisteners.CustomTestExecutionListener

4.3. Порядок реализации TestExecutionListener по умолчанию

Когда Spring обнаруживает реализации TestExecutionListener по умолчанию через механизм SpringFactoriesLoader , он сортирует их с помощью Spring AnnotationAwareOrderComparator. Это учитывает интерфейс Ordered Spring и аннотацию @Order для заказа.

Обратите внимание, что все реализации TestExecutionListener по умолчанию, предоставляемые Spring, реализуют Ordered с соответствующими значениями. Поэтому мы должны убедиться, что наша пользовательская реализация TestExecutionListener зарегистрирована в правильном порядке. Следовательно, мы реализовали Ordered в нашем пользовательском слушателе:

public class CustomTestExecutionListener implements TestExecutionListener, Ordered {
// ...
@Override
public int getOrder() {
return Integer.MAX_VALUE;
};
}

Но вместо этого мы можем использовать аннотацию @Order .

5. Вывод

В этой статье мы увидели, как реализовать собственный TestExecutionListener . Мы также рассмотрели прослушиватели по умолчанию, предоставляемые инфраструктурой Spring.

И, конечно же, код, сопровождающий эту статью, доступен на GitHub.