Интеграционное тестирование 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 мог запускать влож