Интеграционное тестирование Kotlin с Docker: продвинутый уровень

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

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

Начать курс

Интеграционное тестирование Kotlin с Docker: продвинутый уровень

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

1. Основы: Testcontainers с Kotlin DSL

Testcontainers — библиотека для Java/Kotlin, которая управляет жизненным циклом Docker-контейнеров внутри тестов. Для Kotlin доступен удобный DSL, упрощающий конфигурацию.

// build.gradle.kts
dependencies {
    testImplementation("org.testcontainers:testcontainers:1.20.0")
    testImplementation("org.testcontainers:postgresql:1.20.0")
    testImplementation("org.jetbrains.kotlin:kotlin-test")
}

Пример теста с PostgreSQL:

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

@Testcontainers
class UserRepositoryTest {

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

    @Test
    fun `should connect to database`() {
        val jdbcUrl = postgres.jdbcUrl
        // Используем jdbcUrl в DataSource
        println("Connected to: $jdbcUrl")
    }
}

2. Параллельный запуск контейнеров

Для ускорения тестов можно запускать контейнеры параллельно. Однако важно учитывать конфликты портов. Используйте динамические порты и синхронизацию через GenericContainer.

import org.testcontainers.containers.GenericContainer
import org.testcontainers.containers.Network
import org.testcontainers.containers.wait.strategy.Wait
import java.time.Duration

class ParallelContainerTest {

    companion object {
        private val network = Network.newNetwork()

        private val redis1 = GenericContainer<Nothing>("redis:7-alpine")
            .withNetwork(network)
            .withNetworkAliases("redis1")
            .withExposedPorts(6379)
            .waitingFor(Wait.forListeningPort())

        private val redis2 = GenericContainer<Nothing>("redis:7-alpine")
            .withNetwork(network)
            .withNetworkAliases("redis2")
            .withExposedPorts(6379)
            .waitingFor(Wait.forListeningPort())

        init {
            redis1.start()
            redis2.start()
        }
    }

    @Test
    fun `parallel containers test`() {
        val port1 = redis1.getMappedPort(6379)
        val port2 = redis2.getMappedPort(6379)
        println("Redis1 port: $port1, Redis2 port: $port2")
    }
}

Используйте Network для изоляции и waitingFor для ожидания готовности контейнера.

3. Docker Compose в тестах

Для сложных сценариев (база данных + брокер сообщений + кэш) удобно использовать DockerComposeContainer. Он загружает docker-compose.yml и управляет всеми сервисами.

import org.testcontainers.containers.DockerComposeContainer
import java.io.File

class FullStackTest {

    companion object {
        private val compose = DockerComposeContainer<Nothing>(
            File("src/test/resources/docker-compose.yml")
        ).apply {
            withExposedService("postgres_1", 5432)
            withExposedService("redis_1", 6379)
            withLocalCompose(true)
            start()
        }
    }

    @Test
    fun `full integration test`() {
        val pgPort = compose.getServicePort("postgres_1", 5432)
        val redisPort = compose.getServicePort("redis_1", 6379)
        println("Postgres port: $pgPort, Redis port: $redisPort")
    }
}

Пример docker-compose.yml:

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

4. Отладка и логирование контейнеров

При падении тестов важно видеть логи контейнеров. Используйте Slf4jLogConsumer или toString() для вывода.

import org.testcontainers.containers.output.Slf4jLogConsumer
import org.slf4j.LoggerFactory

class DebugContainerTest {

    companion object {
        private val logger = LoggerFactory.getLogger(DebugContainerTest::class.java)

        private val container = GenericContainer<Nothing>("nginx:alpine")
            .withExposedPorts(80)
            .apply {
                start()
                followOutput(Slf4jLogConsumer(logger))
            }
    }

    @Test
    fun `debug test`() {
        val logs = container.logs
        println("Container logs: $logs")
    }
}

Также полезно использовать withStartupTimeout(Duration.ofSeconds(60)) для предотвращения зависаний.

5. Продвинутые трюки: кастомные образы и скрипты

Иногда требуется запустить контейнер с предварительно выполненными скриптами. Используйте withCopyFileToContainer или кастомные Dockerfile.

import org.testcontainers.containers.BindMode
import org.testcontainers.images.builder.ImageFromDockerfile

class CustomImageTest {

    companion object {
        private val customImage = ImageFromDockerfile()
            .withDockerfileFromBuilder { builder ->
                builder
                    .from("openjdk:17-slim")
                    .copy("app.jar", "/app.jar")
                    .entryPoint("java", "-jar", "/app.jar")
            }

        private val container = GenericContainer<Nothing>(customImage)
            .withExposedPorts(8080)
            .withFileSystemBind("./build/libs/app.jar", "/app.jar", BindMode.READ_ONLY)
    }

    @Test
    fun `custom image test`() {
        container.start()
        val port = container.getMappedPort(8080)
        println("Custom app running on port: $port")
    }
}

Заключение

Интеграционное тестирование Kotlin с Docker — мощный инструмент для проверки реального поведения приложения. Продвинутые техники, такие как параллельные контейнеры, Docker Compose, отладка и кастомные образы, позволяют создавать надёжные и быстрые тесты. Используйте Testcontainers с Kotlin DSL для автоматизации и изоляции окружения. Помните о динамических портах и правильном управлении ресурсами — это обеспечит стабильность ваших тестов.

Эти методы помогут вам перейти на новый уровень тестирования и избежать ти

Блоги

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