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

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

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

Начать курс

Введение: зачем нужно интеграционное тестирование с Docker?

При разработке современных приложений на Kotlin, особенно микросервисов, важно проверять, как компоненты системы взаимодействуют друг с другом и с внешними зависимостями (базами данных, очередями сообщений, кэшами). Модульные тесты изолируют код, но не гарантируют, что приложение корректно работает в реальном окружении.

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

  • Воспроизводимость: тесты всегда запускаются в одинаковом окружении.
  • Изоляцию: контейнеры не влияют на локальную среду разработчика.
  • Скорость: контейнеры запускаются за секунды, а не минуты.

В этой статье мы разберём, как настроить интеграционное тестирование Kotlin-приложения с использованием Docker, библиотеки Testcontainers и Docker Compose. Вы получите готовые примеры кода и лучшие практики.

1. Настройка проекта Kotlin для интеграционных тестов

1.1. Зависимости в build.gradle.kts

Для начала добавьте необходимые зависимости в ваш файл build.gradle.kts. Мы будем использовать JUnit 5, Testcontainers и драйвер для PostgreSQL:

dependencies {
    // Основные зависимости приложения
    implementation("org.jetbrains.kotlin:kotlin-stdlib")
    implementation("org.postgresql:postgresql:42.7.1")

    // Тестирование
    testImplementation("org.junit.jupiter:junit-jupiter:5.10.1")
    testImplementation("org.testcontainers:testcontainers:1.19.3")
    testImplementation("org.testcontainers:postgresql:1.19.3")
    testImplementation("org.testcontainers:junit-jupiter:1.19.3")
}

tasks.test {
    useJUnitPlatform()
}

1.2. Базовая структура тестового класса

Создадим простой класс для интеграционного теста, который будет поднимать контейнер PostgreSQL:

import org.junit.jupiter.api.Test
import org.testcontainers.containers.PostgreSQLContainer
import org.testcontainers.junit.jupiter.Container
import org.testcontainers.junit.jupiter.Testcontainers
import java.sql.DriverManager

@Testcontainers
class UserRepositoryIntegrationTest {

    companion object {
        @Container
        val postgres = PostgreSQLContainer<Nothing>("postgres:15-alpine")
            .withDatabaseName("testdb")
            .withUsername("test")
            .withPassword("test")
    }

    @Test
    fun `should connect to database and execute query`() {
        val jdbcUrl = postgres.jdbcUrl
        val username = postgres.username
        val password = postgres.password

        val connection = DriverManager.getConnection(jdbcUrl, username, password)
        val statement = connection.createStatement()

        statement.execute("CREATE TABLE IF NOT EXISTS users (id SERIAL PRIMARY KEY, name VARCHAR(100))")
        statement.execute("INSERT INTO users (name) VALUES ('Alice')")

        val resultSet = statement.executeQuery("SELECT COUNT(*) FROM users")
        resultSet.next()
        val count = resultSet.getInt(1)

        assert(count == 1)
        println("Тест пройден: найдено $count пользователей")
    }
}

Обратите внимание: аннотация @Testcontainers автоматически управляет жизненным циклом контейнера. Он запускается перед всеми тестами в классе и останавливается после их завершения.

2. Тестирование с несколькими контейнерами (Docker Compose)

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

2.1. Файл docker-compose-test.yml

version: '3.8'
services:
  postgres:
    image: postgres:15-alpine
    environment:
      POSTGRES_DB: testdb
      POSTGRES_USER: test
      POSTGRES_PASSWORD: test
    ports:
      - "5432"

  redis:
    image: redis:7-alpine
    ports:
      - "6379"

2.2. Использование DockerComposeContainer в тесте

import org.junit.jupiter.api.Test
import org.testcontainers.containers.DockerComposeContainer
import org.testcontainers.junit.jupiter.Container
import org.testcontainers.junit.jupiter.Testcontainers
import java.io.File

@Testcontainers
class MultiServiceIntegrationTest {

    companion object {
        @Container
        val environment = DockerComposeContainer<Nothing>(
            File("src/test/resources/docker-compose-test.yml")
        ).withExposedService("postgres_1", 5432)
         .withExposedService("redis_1", 6379)
    }

    @Test
    fun `should connect to postgres and redis`() {
        // Получаем динамический порт для PostgreSQL
        val postgresPort = environment.getServicePort("postgres_1", 5432)
        val redisPort = environment.getServicePort("redis_1", 6379)

        println("PostgreSQL доступен на порту: $postgresPort")
        println("Redis доступен на порту: $redisPort")

        // Здесь можно подключаться к сервисам и выполнять тесты
        assert(postgresPort > 0)
        assert(redisPort > 0)
    }
}

Testcontainers автоматически сопоставляет контейнеры с именами сервисов из Compose-файла. Вы можете получить динамические порты, чтобы избежать конфликтов.

3. Интеграционное тестирование с репозиториями и Spring Boot

Если вы используете Spring Boot, интеграционные тесты можно упростить с помощью аннотации @SpringBootTest и Testcontainers. Рассмотрим пример тестирования JPA-репозитория.

3.1. Конфигурация тестового профиля

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

spring:
  datasource:
    url: jdbc:tc:postgresql:15-alpine:///testdb
    username: test
    password: test
  jpa:
    hibernate:
      ddl-auto: create-drop

Обратите внимание на URL: jdbc:tc:postgresql:.... Это специальный JDBC-URL от Testcontainers, который автоматически запускает контейнер при старте приложения.

3.2. Тест репозитория

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.ActiveProfiles
import javax.persistence.Entity
import javax.persistence.GeneratedValue
import javax.persistence.GenerationType
import javax.persistence.Id

@Entity
data class Product(
    @Id @GeneratedValue(strategy = Generation                

Блоги

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