Что такое Testcontainers и как их использовать для интеграционного тестирования?

Ответ

Testcontainers — это Java-библиотека, которая позволяет запускать одноразовые экземпляры инфраструктурных сервисов (БД, брокеры сообщений и т.д.) в Docker-контейнерах прямо из JUnit-тестов.

Основные преимущества:

  • Реальные зависимости: Тесты работают с настоящими СУБД (PostgreSQL, MySQL), а не с in-memory аналогами (H2), что повышает достоверность.
  • Изоляция: Каждый тестовый класс или метод может получить свой чистый экземпляр.
  • Упрощение: Не требуются предустановленные и настроенные внешние сервисы на машине разработчика или CI/CD.

Пример использования с JUnit 5 и PostgreSQL:

@Testcontainers // Аннотация для интеграции с JUnit 5
@DataJpaTest // Аннотация Spring Boot для тестов JPA
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
class UserRepositoryIntegrationTest {

    @Container // Запускает контейнер перед всеми тестами в классе
    private static final PostgreSQLContainer<?> postgres = 
        new PostgreSQLContainer<>("postgres:15-alpine")
            .withDatabaseName("testdb")
            .withUsername("test")
            .withPassword("test");

    @DynamicPropertySource // Динамически переопределяет свойства Spring
    static void configureProperties(DynamicPropertyRegistry registry) {
        registry.add("spring.datasource.url", postgres::getJdbcUrl);
        registry.add("spring.datasource.username", postgres::getUsername);
        registry.add("spring.datasource.password", postgres::getPassword);
    }

    @Autowired
    private UserRepository userRepository;

    @Test
    void shouldSaveAndRetrieveUser() {
        User user = new User("John Doe");
        userRepository.save(user);
        assertThat(userRepository.findAll()).hasSize(1);
    }
}

Поддерживаемые технологии: Помимо баз данных, Testcontainers поддерживает Redis, Kafka, Elasticsearch, Selenium и многие другие. Существуют готовые модули для удобной настройки.

Недостаток: Тесты выполняются медленнее из-за времени на загрузку Docker-образов и запуск контейнеров.

Ответ 18+ 🔞

Слушай, а есть такая штука — Testcontainers. Представь себе, ты пишешь тесты на Java, и тебе нужна настоящая база данных, а не эта поебень in-memory, которая в половине случаев ведёт себя не так, как продакшен. Так вот, эта библиотека — как волшебный пендель. Она поднимает для тебя реальные сервисы в Docker-контейнерах прямо во время прогона JUnit-тестов. Не надо ничего ставить на свою машину, не надо ебаться с настройками. Всё само всплывает, отрабатывает и накрывается медным тазом.

Что за плюсы, спросишь?

  • Настоящая хуйня, а не эмулятор: Ты тестируешься на реальном PostgreSQL или MySQL, а не на H2, который в какой-то момент может тебе выдать "а вот тут я работаю иначе, сука". Достоверность — заебись.
  • Изоляция полная: Каждый тестовый класс может получить свою личную, девственно чистую базу. Никаких левых данных от прошлых прогонов.
  • Проще некуда: Забудь про мануалы "как поднять базу для тестов на дженкинсе". Всё описано в коде, и любой, у кого есть Docker, сможет это запустить.

Смотри, как это выглядит на практике с JUnit 5 и PostgreSQL:

@Testcontainers // Эта хуйня говорит JUnit: "Держись, ща будут контейнеры"
@DataJpaTest // Spring Boot аннотация для тестов JPA
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
class UserRepositoryIntegrationTest {

    @Container // Контейнер запустится один раз на весь класс тестов
    private static final PostgreSQLContainer<?> postgres =
        new PostgreSQLContainer<>("postgres:15-alpine")
            .withDatabaseName("testdb")
            .withUsername("test")
            .withPassword("test");

    @DynamicPropertySource // А это — магия подмены свойств Spring на лету
    static void configureProperties(DynamicPropertyRegistry registry) {
        registry.add("spring.datasource.url", postgres::getJdbcUrl);
        registry.add("spring.datasource.username", postgres::getUsername);
        registry.add("spring.datasource.password", postgres::getPassword);
    }

    @Autowired
    private UserRepository userRepository;

    @Test
    void shouldSaveAndRetrieveUser() {
        User user = new User("John Doe");
        userRepository.save(user);
        assertThat(userRepository.findAll()).hasSize(1); // И всё работает, ёпта!
    }
}

Что ещё умеет? Да почти всё! Redis, Kafka, Elasticsearch, Selenium — для всего есть готовые модули, чтобы не ебать себе мозг ручной настройкой.

Но есть, блядь, и ложка дёгтя: Тесты начинают тормозить. Потому что нужно время, чтобы стянуть Docker-образ (если его нет) и запустить контейнер. Так что если у тебя тысяча быстрых юнит-тестов, не надо в них это пихать. Это инструмент для интеграционных тестов, где важна точность, а не скорость.