Go to file
2021-07-26 22:19:20 +07:00
build_utils@a7655bc60c init project 2021-07-26 13:41:04 +07:00
src add testcontainers annotations (#2) 2021-07-26 15:13:36 +07:00
.gitignore init project 2021-07-26 13:41:04 +07:00
.gitmodules init project 2021-07-26 13:41:04 +07:00
Jenkinsfile init project 2021-07-26 13:41:04 +07:00
LICENSE init project 2021-07-26 13:41:04 +07:00
pom.xml add testcontainers annotations (#2) 2021-07-26 15:13:36 +07:00
README.md Update README.md (#4) 2021-07-26 22:19:20 +07:00

testcontainers-annotations

Библиотека с аннотациями, которые позволяют безшовно подключить внешние докер-контейнеры к интеграционным SpringBoot тестам
Аннотации являются обертками над TestContainers

🚨🚨🚨Работает только с JUnit 5

Пример использование инструмента можно посмотреть в src/test у сервиса magista

Далее речь идет только о кейсах, когда есть необходимость в интеграционных тестах с внешними зависимостями


Аннотации

На данный момент для библиотеки реализована поддержка 2 контейнеров — postgres, confluentinc/cp-kafka

Базовые аннотации для использования:

@PostgresqlTestcontainer
@KafkaTestcontainer

Инициализация настроек контейнеров в спринговый контекст тестового приложения реализован под капотом аннотаций

Детали  

Инициализация настроек контейнеров в спринговый контекст тестового приложения реализован под капотом аннотаций, на уровне реализации интерфейса ContextCustomizerFactory — информация о настройках используемого тестконтейнера и передаваемые через параметры аннотации настройки инициализируются через TestPropertyValues и сливаются с текущим получаемым контекстом приложения ConfigurableApplicationContext Инициализация кастомизированных фабрик с инициализацией настроек осуществляется через описание бинов в файле spring.factories

@PostgresqlTestcontainer

Аннотация не требует дополнительной конфигурации, при ее использовании будет поднять тестконтейнер с базой + настройки контейнера будут проинициализированы в контекст тестового приложения

Параметры аннотации

InstanceMode instanceMode() default InstanceMode.DEFAULT;

instanceMode() описывает жизненный цикл аннотации — аннотация может быть использована в рамках одного тестового класса (InstanceMode.DEFAULT), который ее использует, либо использована в рамках всего набора тестовых классов в пакете test (InstanceMode.SINGLETON) — здесь будет создаваться синглтон, при котором каждый следующий тест переиспользует уже созданный тестконтейнер с базой

String[] properties() default {};

properties() аналогичный параметр как у аннотации SpringBootTest, например — properties = {"kek=true"}

Дополнительные обертки

@PostgresqlTestcontainerSingleton@PostgresqlTestcontainer в режиме InstanceMode.SINGLETON
@WithPostgresqlSpringBootITest — обертка для запуска спрингового теста с использованием тестконтейнера с базой. На борту — @PostgresqlTestcontainer и @DefaultSpringBootTest (представляет из себя дефолтную обертку над SpringBootTest типичную для домена rbkmoney)
@WithPostgresqlSingletonSpringBootITest — аналог @WithPostgresqlSpringBootITest только с @PostgresqlTestcontainerSingleton

Примеры использования

@PostgresqlTestcontainer
@SpringBootTest
public class AdjustmentDaoTest {

    @Autowired
    private AdjustmentDao adjustmentDao;

  ...

@PostgresqlTestcontainerSingleton
@DefaultSpringBootTest
public class AdjustmentDaoTest {

    @Autowired
    private AdjustmentDao adjustmentDao;

  ...

@WithPostgresqlSpringBootITest
public class AdjustmentDaoTest {

    @Autowired
    private AdjustmentDao adjustmentDao;

  ...

@KafkaTestcontainer

При ее использовании будет поднять тестконтейнер с кафкой + настройки контейнера будут проинициализированы в контекст тестового приложения
Аннотация требует дополнительной конфигурации (см. ниже)

Параметры аннотации

InstanceMode instanceMode() default InstanceMode.DEFAULT;

instanceMode() описывает жизненный цикл аннотации — аннотация может быть использована в рамках одного тестового класса (InstanceMode.DEFAULT), который ее использует, либо использована в рамках всего набора тестовых классов в пакете test (InstanceMode.SINGLETON) — здесь будет создаваться синглтон, при котором каждый следующий тест переиспользует уже созданный тестконтейнер с кафкой

String[] properties() default {};

properties() аналогичный параметр как у аннотации SpringBootTest, например — properties = {"kafka.topics.invoicing.consume.enabled=true"}

String[] topicsKeys();

topicsKeys() при использовании аннотации данный параметр обязателен для конфигурации — здесь перечисляются ключи пропертей, которые являются параметрами с названиями топиков, которые требуется создать при старте кафки (создание топиков происходит через AdminClient, также есть дополнительная валидация результатов создания топиков, генерируется исключение в случае фейла процесса), например — topicsKeys = {"kafka.topics.invoicing.id"}

Дополнительные обертки

Здесь дополнительные обертки не реализованы в силу обязательной предконфигурации базовой аннотации с параметрами конкретного приложения

Демо обертки  

Хоть возможности создать набор оберток нет, но есть примеры, как это может выглядеть, находятся внутри пакета com.rbkmoney.testcontainers.annotations.kafka.demo

@DemoKafkaTestcontainer — пример имплементации @KafkaTestcontainer
@DemoKafkaTestcontainerSingleton — пример имплементации @KafkaTestcontainer в режиме InstanceMode.SINGLETON
@DemoWithKafkaSpringBootITest — обертка для запуска спрингового теста с использованием тестконтейнера с кафкой. На борту — @DemoKafkaTestcontainer и @KafkaProducerSpringBootTest (представляет из себя обертку над SpringBootTest типичную для домена rbkmoney c KafkaProducerConfig который представляет инструменты для тестирования потребителей)
@DemoWithKafkaSingletonSpringBootITest — аналог @DemoWithKafkaSpringBootITest только с @DemoKafkaTestcontainerSingleton

Примеры использования

Как это используется в магисте:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@KafkaTestcontainer(
        instanceMode = KafkaTestcontainer.InstanceMode.SINGLETON,
        properties = {
                "kafka.topics.invoicing.consume.enabled=true",
                "kafka.topics.invoice-template.consume.enabled=true",
                "kafka.topics.pm-events-payout.consume.enabled=true",
                "kafka.state.cache.size=0"},
        topicsKeys = {
                "kafka.topics.invoicing.id",
                "kafka.topics.invoice-template.id",
                "kafka.topics.pm-events-payout.id"})
public @interface MagistaKafkaTestcontainerSingleton {
}

@KafkaTestcontainer имплементируется с параметрами магисты для кафки (используется синглтон для реиспользование текущего тестконтейнера и понижения времени прогона тестов на CI)

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@PostgresqlTestcontainerSingleton
@MagistaKafkaTestcontainerSingleton
@KafkaProducerSpringBootTest
public @interface MagistaSpringBootITest {
}

Создается обертка в виде аннотации, которая при использовании по очереди поднимает базу, кафку (нашу имплементированную аннотацию) и SpringBootTest для запуска спрингового контекста (@KafkaProducerSpringBootTest представляет из себя обертку над SpringBootTest типичную для домена rbkmoney c KafkaProducerConfig который представляет инструменты для тестирования потребителей)

Ресерч

Было

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

Класс-родитель с конфигом для тестов, для которых является необходимым использования PostgreSQL в качестве внешней зависимости:

AbstractPostgreTestContainerConfig.java  

@SpringBootTest
@Testcontainers
@DirtiesContext
@ContextConfiguration(classes = Application.class,
        initializers = Initializer.class)
public abstract class AbstractPostgreTestContainerConfig {

    private static final String POSTGRESQL_IMAGE_NAME = "postgres";
    private static final String POSTGRESQL_VERSION = "9.6";

    @Container
    public static final PostgreSQLContainer DB = new PostgreSQLContainer(DockerImageName
            .parse(POSTGRESQL_IMAGE_NAME)
            .withTag(POSTGRESQL_VERSION));

    public static class Initializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
        @Override
        public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
            TestPropertyValues.of(
                    "spring.datasource.url=" + DB.getJdbcUrl(),
                    "spring.datasource.username=" + DB.getUsername(),
                    "spring.datasource.password=" + DB.getPassword(),
                    "flyway.url=" + DB.getJdbcUrl(),
                    "flyway.user=" + DB.getUsername(),
                    "flyway.password=" + DB.getPassword()
            ).applyTo(configurableApplicationContext);
        }
    }

}
  

К плюсам данного решения можно отнести тот факт, что сами тесты становятся более читаемым, в которых нет ничего лишнего, кроме покрытия бизнес-логики приложения

Тогда типичный тест Dao слоя будет выглядеть как:

PaymentDaoTest.java  

class PaymentDaoTest extends AbstractPostgreTestContainerConfig {

    @Autowired
    PaymentDao paymentDao;
  
  ...

}

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

Стало

При использовании testcontainers-annotations для подключения внешней зависимости в файл с тестом необходимо добавить требуемую аннотацию и задать нужный для теста конфиг SpringBootTest

Типичный тест Dao слоя, для которого является необходимым использования PostgreSQL в качестве внешней зависимости, будет выглядеть как тест, для вызова которого требуется только @PostgresqlTestcontainer и @SpringBootTest:

AdjustmentDaoTest.java  

@PostgresqlTestcontainer
@SpringBootTest
public class AdjustmentDaoTest {

    @Autowired
    private AdjustmentDao adjustmentDao;

  ...

Либо воспользоваться готовой оберткой из библиотеки @WithPostgresqlSingletonSpringBootITest:

AdjustmentDaoTest.java  

@WithPostgresqlSingletonSpringBootITest
public class AdjustmentDaoTest {

    @Autowired
    private AdjustmentDao adjustmentDao;

  ...