Интеграционное тестирование Java с Docker: полное руководство

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Интеграционное тестирование Java с Docker: от А до Я

Интеграционное тестирование — ключевой этап разработки, позволяющий проверить взаимодействие компонентов приложения с внешними системами: базами данных, очередями сообщений, REST-сервисами. Традиционный подход с использованием H2 или встроенных серверов часто приводит к расхождениям между тестовой и продакшен-средами. Решение — Docker. В этой статье мы разберем, как настроить интеграционное тестирование Java-приложений с Docker, используя библиотеку Testcontainers и Docker Compose. Вы узнаете, как писать тесты, которые работают с реальными контейнерами, и как интегрировать их в CI/CD.

Почему Docker для интеграционных тестов?

Docker позволяет поднимать изолированные контейнеры с точными версиями зависимостей (PostgreSQL, Redis, Kafka) непосредственно перед тестами и уничтожать их после. Это гарантирует:

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

Настройка проекта с Testcontainers

Testcontainers — это Java-библиотека, управляющая жизненным циклом Docker-контейнеров. Добавим зависимость в pom.xml (Maven):

<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>testcontainers</artifactId>
    <version>1.19.3</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>postgresql</artifactId>
    <version>1.19.3</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

Для Gradle добавьте в build.gradle:

testImplementation 'org.testcontainers:testcontainers:1.19.3'
testImplementation 'org.testcontainers:postgresql:1.19.3'

Пример: тестирование репозитория с PostgreSQL

Рассмотрим простой сервис для работы с пользователями. Создадим тест, который поднимает контейнер PostgreSQL и проверяет сохранение данных.

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;

import static org.assertj.core.api.Assertions.assertThat;

@Testcontainers
@SpringBootTest
class UserRepositoryTest {

    @Container
    static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15")
            .withDatabaseName("testdb")
            .withUsername("test")
            .withPassword("test");

    @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 shouldSaveAndFindUser() {
        User user = new User("john_doe", "john@example.com");
        userRepository.save(user);

        User found = userRepository.findByUsername("john_doe");
        assertThat(found).isNotNull();
        assertThat(found.getEmail()).isEqualTo("john@example.com");
    }
}

Разбор кода:

  • @Testcontainers — активирует управление контейнерами через JUnit 5.
  • @Container — аннотация для статического поля контейнера. Testcontainers автоматически запустит его перед тестами и остановит после.
  • @DynamicPropertySource — переопределяет свойства Spring Boot, подставляя JDBC URL контейнера.
  • Тест проверяет, что пользователь сохраняется и находится по имени.

Работа с несколькими контейнерами и Docker Compose

В реальных проектах часто требуется одновременно запускать PostgreSQL, Redis и Kafka. Testcontainers поддерживает Docker Compose:

import org.testcontainers.containers.DockerComposeContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;

import java.io.File;

@Testcontainers
class MultiServiceIntegrationTest {

    @Container
    static DockerComposeContainer<?> environment = new DockerComposeContainer<>(
            new File("src/test/resources/docker-compose.yml"))
            .withExposedService("postgres", 5432)
            .withExposedService("redis", 6379);

    @Test
    void testWithMultipleContainers() {
        String jdbcUrl = "jdbc:postgresql://localhost:" +
                environment.getServicePort("postgres", 5432) + "/testdb";
        // Используйте jdbcUrl для настройки DataSource
    }
}

Пример docker-compose.yml:

version: '3.8'
services:
  postgres:
    image: postgres:15
    environment:
      POSTGRES_DB: testdb
      POSTGRES_USER: test
      POSTGRES_PASSWORD: test
  redis:
    image: redis:7-alpine

Интеграция с CI/CD (GitHub Actions)

Чтобы тесты работали в CI, нужно, чтобы Docker был доступен. В GitHub Actions это встроено:

name: Java CI with Testcontainers
on: [push]
jobs:
  test:
    runs-on: ubuntu-latest
    services:
      docker:
        image: docker:24.0.7
        options: --privileged
    steps:
      - uses: actions/checkout@v4
      - name: Set up JDK 17
        uses: actions/setup-java@v4
        with:
          java-version: '17'
          distribution: 'temurin'
      - name: Run tests
        run: mvn test

Важные моменты:

  • Используйте --privileged для контейнера Docker, чтобы Testcontainers мог запускать влож

Блоги

Рекомендаций по книгам