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

Ответ

Testcontainers — это Java-библиотека, которая предоставляет легковесные, одноразовые экземпляры баз данных, брокеров сообщений или других сервисов, запускаемых в Docker-контейнерах. Это позволяет проводить интеграционные тесты в среде, максимально приближенной к продакшену.

Типичный сценарий использования с Spring Boot и PostgreSQL:

@Testcontainers // Аннотация JUnit Jupiter
@SpringBootTest
public class UserRepositoryIntegrationTest {

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

    // Динамически подставляет параметры подключения в Spring контекст
    @DynamicPropertySource
    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("test@email.com", "Test User");
        userRepository.save(user);
        assertThat(userRepository.findById(user.getId())).isPresent();
    }
}

Ключевые преимущества:

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

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

Ответ 18+ 🔞

Ну вот, сидишь ты такой, пишешь свои тесты, и тут — бац! — понимаешь, что твой любимый H2 ведёт себя как последняя мартышлюшка: вроде и SQL понимает, а на деле — нихуя не так, как продакшен-постгре. И начинается: «А вот на проде работало!». Да, блядь, работало, потому что там не H2, а нормальная база!

Так вот, чтобы не выглядеть потом мудаком, есть штука Testcontainers. Это такая Java-библиотека, которая, по сути, говорит: «Да похуй, я тебе на каждый тест разверну в Docker-контейнере настоящую, живую, ебучую базу данных, брокер сообщений или что ты там ещё захочешь». И всё это — одноразовое, чистое, изолированное. Красота же, ёпта!

Смотри, как это выглядит в деле с Spring Boot и PostgreSQL. Никакой магии, только чистый код и немного аннотаций:

@Testcontainers // Эта хуйня говорит JUnit: «Держись, ща контейнеры попрут»
@SpringBootTest
public class UserRepositoryIntegrationTest {

    // Вот он, наш боевой постгрес в альпийской линеечке. Запустится перед всеми тестами.
    @Container
    static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15-alpine");

    // А это — хитрая жопа, чтобы Spring подхватил настройки подключения на лету.
    @DynamicPropertySource
    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("test@email.com", "Test User");
        userRepository.save(user);
        assertThat(userRepository.findById(user.getId())).isPresent();
    }
}

И в чём же, блядь, соль, спросишь ты?

  • Реализм, ёбаный в рот! Ты тестируешься не на какой-то там игрушечной in-memory базе, а на реальном постгресе. Все нюансы, все диалекты SQL, все особенности — на месте. Никаких сюрпризов на проде.
  • Изоляция. Каждый тест — свой свеженький контейнер. Один тест насрал — другому не пахнет. Всё чисто.
  • Для CI/CD — просто песня. На сервере сборки нужен только Docker. Никаких предустановленных баз, брокеров и прочей хуйни. Всё подтянется само.
  • Автоматизация. Запустился, протестировался, контейнер убился. Красота!

Но не всё так гладко, конечно. Главный косяк — теперь они медленнее. Ну а хули ты хотел? Запуск контейнера — это не мгновенно. Так что если у тебя тысяча быстрых юнит-тестов, не надо в них это пихать. Это инструмент для серьёзных, ебальных интеграционных тестов.

Короче, если не хочешь, чтобы твой код на проде встал как вкопанный из-за разницы в поведении базы, — бери Testcontainers. Оно того стоит, ёпта.