Интеграционное тестирование в Spring: TestContext Framework, конфигурация контекста с профилями среды
Spring Framework имеет первоклассную поддержку понятий сред и профилей (также известных как "профили определения bean-компонентов"), а интеграционные тесты можно настроить для активации определенных профилей определения bean-компонентов для различных сценариев тестирования. Это достигается путем аннотирования тестового класса аннотацией @ActiveProfiles и предоставления списка профилей, которые должны быть активированы при загрузке ApplicationContext для теста.
Вы можете использовать @ActiveProfiles с любой реализацией SPI SmartContextLoader, но @ActiveProfiles не поддерживается с реализациями более старого SPI ContextLoader.
Рассмотрим два примера с конфигурацией XML и классами @Configuration:
<!-- app-config.xml -->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:jee="http://www.springframework.org/schema/jee"
xsi:schemaLocation="...">
<bean id="transferService"
class="com.bank.service.internal.DefaultTransferService">
<constructor-arg ref="accountRepository"/>
<constructor-arg ref="feePolicy"/>
</bean>
<bean id="accountRepository"
class="com.bank.repository.internal.JdbcAccountRepository">
<constructor-arg ref="dataSource"/>
</bean>
<bean id="feePolicy"
class="com.bank.service.internal.ZeroFeePolicy"/>
<beans profile="dev">
<jdbc:embedded-database id="dataSource">
<jdbc:script
location="classpath:com/bank/config/sql/schema.sql"/>
<jdbc:script
location="classpath:com/bank/config/sql/test-data.sql"/>
</jdbc:embedded-database>
</beans>
<beans profile="production">
<jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>
</beans>
<beans profile="default">
<jdbc:embedded-database id="dataSource">
<jdbc:script
location="classpath:com/bank/config/sql/schema.sql"/>
</jdbc:embedded-database>
</beans>
</beans>
@ExtendWith(SpringExtension.class)
// ApplicationContext будет загружен
// из "classpath: /app-config.xml"
@ContextConfiguration("/app-config.xml")
@ActiveProfiles("dev")
class TransferServiceTest {
@Autowired
TransferService transferService;
@Test
void testTransferService() {
// тестируем transferService
}
}
При запуске TransferServiceTest его ApplicationContext загружается из файла конфигурации app-config.xml в корне пути к классам. Если вы проверите app-config.xml, вы увидите, что bean-компонент accountRepository имеет зависимость от bean-компонента dataSource. Однако dataSource не определен как bean-компонент верхнего уровня. Вместо этого dataSource определяется трижды: в профиле production, в профиле dev и в профиле по умолчанию (default).
Аннотируя TransferServiceTest с помощью @ActiveProfiles("dev"), мы инструктируем Spring TestContext Framework загрузить ApplicationContext с активными профилями, установленными на {"dev"}. В результате создается встроенная база данных, которая заполняется тестовыми данными, а компонент accountRepository связан со ссылкой на источник данных разработки (development DataSource). Вероятно, это то, что мы хотим в интеграционном тесте.
Иногда бывает полезно назначить bean-компоненты профилю по умолчанию. Компоненты в профиле по умолчанию включаются только тогда, когда никакой другой профиль специально не активирован. Вы можете использовать это для определения "резервных" bean-компонентов, которые будут использоваться в состоянии приложения по умолчанию. Например, вы можете явно указать источник данных для профилей dev и production, но определить источник данных в памяти по умолчанию, если ни один из них не активен.
В следующих листингах кода показано, как реализовать те же тесты конфигурации и интеграции с классами @Configuration вместо XML:
@Configuration
@Profile("dev")
public class StandaloneDataConfig {
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.addScript("classpath:com/bank/config/sql/test-data.sql")
.build();
}
}
@Configuration
@Profile("production")
public class JndiDataConfig {
@Bean(destroyMethod="")
public DataSource dataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
}
@Configuration
@Profile("default")
public class DefaultDataConfig {
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.build();
}
}
@Configuration
public class TransferServiceConfig {
@Autowired DataSource dataSource;
@Bean
public TransferService transferService() {
return new DefaultTransferService(accountRepository(), feePolicy());
}
@Bean
public AccountRepository accountRepository() {
return new JdbcAccountRepository(dataSource);
}
@Bean
public FeePolicy feePolicy() {
return new ZeroFeePolicy();
}
}
@SpringJUnitConfig({
TransferServiceConfig.class,
StandaloneDataConfig.class,
JndiDataConfig.class,
DefaultDataConfig.class})
@ActiveProfiles("dev")
class TransferServiceTest {
@Autowired
TransferService transferService;
@Test
void testTransferService() {
// тестируем transferService
}
}
В этом варианте мы разделили конфигурацию XML на четыре независимых класса @Configuration:
- TransferServiceConfig: получает dataSource посредством внедрения зависимостей с помощью @Autowired.
- StandaloneDataConfig: определяет dataSource для встроенной базы данных, подходящий для тестов разработчика.
- JndiDataConfig: определяет dataSource, который извлекается из JNDI в производственной среде.
- DefaultDataConfig: определяет dataSource для встроенной базы данных по умолчанию, если профиль не активен.
Как и в случае с примером конфигурации на основе XML, мы по-прежнему аннотируем TransferServiceTest с помощью @ActiveProfiles("dev"), но на этот раз мы указываем все четыре класса конфигурации с помощью аннотации @ContextConfiguration. Само тело тестового класса полностью не изменилось.
Часто один набор профилей используется в нескольких тестовых классах в рамках одного проекта. Таким образом, чтобы избежать дублирования объявлений аннотации @ActiveProfiles, вы можете один раз объявить @ActiveProfiles в базовом классе, и подклассы автоматически наследуют конфигурацию @ActiveProfiles от базового класса. В следующем примере объявление @ActiveProfiles (а также других аннотаций) перемещено в абстрактный суперкласс, AbstractIntegrationTest:
@SpringJUnitConfig({
TransferServiceConfig.class,
StandaloneDataConfig.class,
JndiDataConfig.class,
DefaultDataConfig.class})
@ActiveProfiles("dev")
abstract class AbstractIntegrationTest {
}
// профиль "dev" унаследованный от суперкласса
class TransferServiceTest extends AbstractIntegrationTest {
@Autowired
TransferService transferService;
@Test
void testTransferService() {
// тестируем transferService
}
}
@ActiveProfiles также поддерживает атрибут inheritProfiles, который можно использовать для отключения наследования активных профилей, как показано в следующем примере:
// Профиль "dev" заменен на "production"
@ActiveProfiles(profiles = "production", inheritProfiles = false)
class ProductionTransferServiceTest extends AbstractIntegrationTest {
// тело теста
}
Кроме того, иногда необходимо разрешать активные профили для тестов программно, а не декларативно - например, на основе:
- Текущей операционной системы.
- Выполняются ли тесты на сервере сборки непрерывной интеграции.
- Наличие определенных переменных окружения.
- Наличие настраиваемых аннотаций на уровне класса.
Чтобы разрешить активные профили определения bean-компонентов программным способом, вы можете реализовать настраиваемый ActiveProfilesResolver и зарегистрировать его с помощью атрибута resolver @ActiveProfiles. В следующем примере показано, как реализовать и зарегистрировать пользовательский OperatingSystemActiveProfilesResolver:
// Профиль "dev" заменяется программно
// через специальный преобразователь
@ActiveProfiles(
resolver = OperatingSystemActiveProfilesResolver.class,
inheritProfiles = false)
class TransferServiceTest extends AbstractIntegrationTest {
// тело теста
}
public class OperatingSystemActiveProfilesResolver implements ActiveProfilesResolver {
@Override
public String[] resolve(Class<?> testClass) {
String profile = ...;
// определяем значение профиля в зависимости от операционной системы
return new String[] {profile};
}
}
Читайте также:
- Интеграционное тестирование в Spring: TestContext Framework, конфигурация контекста с инициализаторами контекста
- Интеграционное тестирование в Spring: TestContext Framework, конфигурация контекста с классами компонентов
- Интеграционное тестирование в Spring: TestContext Framework, наследование конфигурации контекста
Комментарии
Отправить комментарий