Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Тест-контейнеры (Testcontainers)
Testcontainers — это Java библиотека, которая обеспечивает лёгкое создание и управление Docker контейнерами во время тестирования. Она позволяет запускать реальные экземпляры баз данных, сообщих брокеров, и других сервисов в контейнерах для интеграционных тестов, вместо использования mock-объектов или in-memory баз данных.
Назначение Testcontainers
Основные задачи:
- Изоляция тестов: Каждый тест получает чистый контейнер
- Реалистичное тестирование: Используются реальные версии БД и сервисов
- Простота настройки: Автоматическая загрузка и запуск контейнеров
- Независимость: Не требуется локально установленная БД
- Reproducibility: Одинаковое окружение на всех машинах
Проблема без Testcontainers
// ❌ Проблемы с традиционным подходом
public class UserRepositoryTest {
// Необходимо:
// 1. Установить PostgreSQL локально
// 2. Создать тестовую базу данных
// 3. Очистить данные между тестами
// 4. Синхронизировать версию БД в проекте
private String jdbcUrl = "jdbc:postgresql://localhost:5432/testdb";
private String username = "testuser";
private String password = "testpass";
@Before
public void setup() {
// Ручная очистка данных
// Сложная настройка
}
}
Решение с Testcontainers
// ✅ С использованием Testcontainers
import org.testcontainers.containers.PostgreSQLContainer;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.BeforeEach;
import static org.testcontainers.containers.wait.strategy.Wait.forListeningPort;
public class UserRepositoryTest {
static PostgreSQLContainer<?> postgres =
new PostgreSQLContainer<>("postgres:15")
.withDatabaseName("testdb")
.withUsername("testuser")
.withPassword("testpass");
static {
postgres.start();
}
private UserRepository userRepository;
private DataSource dataSource;
@BeforeEach
void setUp() {
// DataSource автоматически настроен на контейнер
dataSource = createDataSource(
postgres.getJdbcUrl(),
postgres.getUsername(),
postgres.getPassword()
);
userRepository = new UserRepository(dataSource);
}
@Test
void testSaveAndFindUser() {
User user = new User("john@example.com", "password");
userRepository.save(user);
User found = userRepository.findByEmail("john@example.com");
assertNotNull(found);
assertEquals("john@example.com", found.getEmail());
}
}
Зависимость 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>
<!-- Для MySQL -->
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>mysql</artifactId>
<version>1.19.3</version>
<scope>test</scope>
</dependency>
<!-- Для MongoDB -->
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>mongodb</artifactId>
<version>1.19.3</version>
<scope>test</scope>
</dependency>
Примеры использования разных сервисов
1. PostgreSQL с JUnit 5
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import org.testcontainers.containers.PostgreSQLContainer;
@Testcontainers
public class PostgreSQLTest {
@Container
static PostgreSQLContainer<?> postgres =
new PostgreSQLContainer<>("postgres:15")
.withDatabaseName("testdb")
.withUsername("user")
.withPassword("password");
@Test
void testDatabaseConnection() {
// postgres.getJdbcUrl() возвращает реальный URL контейнера
assertNotNull(postgres.getJdbcUrl());
}
}
2. MySQL
import org.testcontainers.containers.MySQLContainer;
@Testcontainers
public class MySQLTest {
@Container
static MySQLContainer<?> mysql =
new MySQLContainer<>("mysql:8.0")
.withDatabaseName("testdb")
.withUsername("root")
.withPassword("password");
@Test
void testMySQLDatabase() {
String url = mysql.getJdbcUrl();
// Использование URL для подключения
}
}
3. MongoDB
import org.testcontainers.containers.MongoDBContainer;
from pymongo import MongoClient
@Testcontainers
public class MongoDBTest {
@Container
static MongoDBContainer mongodb =
new MongoDBContainer("mongo:6.0")
.withExposedPorts(27017);
@Test
void testMongoConnection() {
String connectionString = mongodb.getReplicaSetUrl();
// Использование connectionString
}
}
4. RabbitMQ
import org.testcontainers.containers.RabbitMQContainer;
@Testcontainers
public class RabbitMQTest {
@Container
static RabbitMQContainer rabbitmq =
new RabbitMQContainer("rabbitmq:3.12")
.withExposedPorts(5672, 15672);
@Test
void testRabbitMQConnection() {
String amqpUrl = rabbitmq.getAmqpUrl();
// Использование для подключения
}
}
5. Redis
import org.testcontainers.containers.GenericContainer;
@Testcontainers
public class RedisTest {
@Container
static GenericContainer<?> redis =
new GenericContainer<>("redis:7")
.withExposedPorts(6379);
@Test
void testRedisConnection() {
Integer port = redis.getFirstMappedPort();
String host = redis.getHost();
// Подключение к Redis
}
}
Полный пример интеграционного теста
import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.DynamicPropertySource;
import org.springframework.beans.factory.annotation.Autowired;
@Testcontainers
@SpringBootTest
public class UserServiceIntegrationTest {
@Container
static PostgreSQLContainer<?> postgres =
new PostgreSQLContainer<>("postgres:15")
.withDatabaseName("testdb")
.withUsername("testuser")
.withPassword("testpass")
.withInitScript("init.sql"); // SQL скрипт для инициализации
@Autowired
private UserService userService;
@Autowired
private UserRepository userRepository;
// Дополнить 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);
}
@Test
void testUserRegistration() {
// Arrange
String email = "newuser@example.com";
String password = "securepass";
// Act
User registeredUser = userService.register(email, password);
// Assert
assertNotNull(registeredUser);
assertEquals(email, registeredUser.getEmail());
User foundUser = userRepository.findByEmail(email);
assertNotNull(foundUser);
}
@Test
void testUserUpdate() {
// Arrange
User user = new User("john@example.com", "password");
userRepository.save(user);
// Act
user.setEmail("john.new@example.com");
userService.update(user);
// Assert
User updated = userRepository.findByEmail("john.new@example.com");
assertNotNull(updated);
}
@Test
void testDuplicateEmailValidation() {
// Arrange
User user1 = new User("test@example.com", "pass1");
userRepository.save(user1);
// Act & Assert
assertThrows(DuplicateEmailException.class, () -> {
userService.register("test@example.com", "pass2");
});
}
}
Инициализация данных
// init.sql
CREATE TABLE users (
id SERIAL PRIMARY KEY,
email VARCHAR(255) UNIQUE NOT NULL,
password VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE posts (
id SERIAL PRIMARY KEY,
user_id INT NOT NULL,
title VARCHAR(255) NOT NULL,
content TEXT,
FOREIGN KEY (user_id) REFERENCES users(id)
);
// В тесте
@Container
static PostgreSQLContainer<?> postgres =
new PostgreSQLContainer<>("postgres:15")
.withDatabaseName("testdb")
.withUsername("testuser")
.withPassword("testpass")
.withInitScript("schema.sql");
Лучшие практики
- Используй @Testcontainers для JUnit 5: Автоматическое управление жизненным циклом
@Testcontainers
public class MyTest {
@Container
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>();
}
-
Переиспользуй контейнеры: Создавай static контейнеры для нескольких тестов
-
Используй @DynamicPropertySource для Spring: Синхронизирует параметры контейнера с приложением
-
Инициализируй данные: Используй SQL скрипты для подготовки БД
-
Минимизируй время запуска: Переиспользуй контейнеры между тестами
-
Изолируй тесты: Очищай данные между тестами или используй транзакции
Альтернативы
- H2 Database: In-memory БД для быстрых тестов
- Mockito/Mock сервисы: Для юнит-тестов
- Docker Compose: Для локального тестирования
Testcontainers — мощный инструмент для написания реалистичных интеграционных тестов, обеспечивающих надежность и воспроизводимость тестового окружения.