← Назад к вопросам

Что такое тест-контейнеры?

2.0 Middle🔥 141 комментариев
#Тестирование

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Тест-контейнеры (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");

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

  1. Используй @Testcontainers для JUnit 5: Автоматическое управление жизненным циклом
@Testcontainers
public class MyTest {
    @Container
    static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>();
}
  1. Переиспользуй контейнеры: Создавай static контейнеры для нескольких тестов

  2. Используй @DynamicPropertySource для Spring: Синхронизирует параметры контейнера с приложением

  3. Инициализируй данные: Используй SQL скрипты для подготовки БД

  4. Минимизируй время запуска: Переиспользуй контейнеры между тестами

  5. Изолируй тесты: Очищай данные между тестами или используй транзакции

Альтернативы

  • H2 Database: In-memory БД для быстрых тестов
  • Mockito/Mock сервисы: Для юнит-тестов
  • Docker Compose: Для локального тестирования

Testcontainers — мощный инструмент для написания реалистичных интеграционных тестов, обеспечивающих надежность и воспроизводимость тестового окружения.

Что такое тест-контейнеры? | PrepBro