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

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

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

Начать курс

Интеграционное тестирование Java-приложений с Docker

Современная Java-разработка немыслима без качественного тестирования. Однако модульные тесты (unit-тесты) часто не способны выявить проблемы, возникающие при взаимодействии компонентов с реальными базами данных, очередями сообщений или внешними API. Именно здесь на помощь приходит интеграционное тестирование, а Docker делает этот процесс простым, воспроизводимым и изолированным. В этой статье мы разберем, как эффективно использовать Docker для интеграционного тестирования Java-приложений, используя библиотеку Testcontainers и Docker Compose.

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

Традиционный подход к интеграционному тестированию часто требует установки и настройки всех зависимостей (баз данных, брокеров сообщений) на локальной машине разработчика или на CI-сервере. Это приводит к ряду проблем:

  • Нестабильность окружения: Разные версии СУБД, конфликты портов, различие между средой разработки и продакшеном.
  • Сложность настройки CI/CD: Необходимость вручную устанавливать и конфигурировать сервисы в пайплайне.
  • Загрязнение данных: Тестовые данные могут оставаться в локальной базе данных, влияя на последующие запуски.
  • Параллелизация: Одновременный запуск тестов на одной машине может приводить к конфликтам.

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

Настройка проекта: Testcontainers vs Docker Compose

Существует два основных подхода к интеграции Docker в тесты Java. Рассмотрим оба.

1. Testcontainers: библиотека для управления контейнерами из кода

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

Преимущества:

  • Программное управление: контейнеры запускаются и останавливаются автоматически.
  • Поддержка популярных сервисов "из коробки" (GenericContainer, PostgreSQLContainer, KafkaContainer и др.).
  • Автоматический выбор свободных портов (избегаем конфликтов).
  • Идеально подходит для модульных интеграционных тестов.

Пример: Тестирование REST-контроллера с PostgreSQL

Добавьте зависимость в ваш 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>

Теперь напишем тест для Spring Boot приложения, которое работает с PostgreSQL:

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.http.ResponseEntity;
import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;

import static org.junit.jupiter.api.Assertions.assertEquals;

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@Testcontainers
public class UserControllerIntegrationTest {

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

    @Autowired
    private TestRestTemplate restTemplate;

    @Test
    public void testGetUser() {
        // Аррange: данные уже вставлены через миграции или @Sql
        // Act
        ResponseEntity<String> response = restTemplate.getForEntity("/api/users/1", String.class);

        // Assert
        assertEquals(200, response.getStatusCodeValue());
        System.out.println("Response: " + response.getBody());
    }
}

В этом примере Testcontainers автоматически запускает контейнер PostgreSQL, Spring Boot подключается к нему (благодаря динамическим свойствам, которые библиотека подставляет в application.properties), и тест выполняется в изолированной среде.

2. Docker Compose: управление несколькими сервисами

Если ваше приложение зависит от нескольких сервисов (например, PostgreSQL + Redis + Keycloak), удобнее использовать Docker Compose. Testcontainers также поддерживает этот подход через DockerComposeContainer.

Пример: Использование Docker Compose в тесте

Создайте файл docker-compose-test.yml:

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

А затем используйте его в тесте:

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.testcontainers.containers.DockerComposeContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;

import java.io.File;

@SpringBootTest
@Testcontainers
public class MultiServiceIntegrationTest {

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

    @Test
    public void testWithMultipleServices() {
        // Ваш тест, который использует оба сервиса
        String postgresHost = environment.getServiceHost("postgres", 5432);
        Integer postgresPort = environment.getServicePort("postgres", 5432);
        System.out.println("PostgreSQL доступен на " + postgresHost + ":" + postgresPort);
    }
}

Этот подход идеален для интеграционных тестов, которые проверяют взаимодействие нескольких микросервисов или сложную инфраструктуру.

Лучшие практи

Блоги

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