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

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

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

Начать курс

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

Интеграционное тестирование — ключевой этап разработки надёжных Go-приложений. В отличие от юнит-тестов, интеграционные проверяют взаимодействие компонентов: базы данных, кэша, внешних API. Docker позволяет изолировать тестовое окружение, делая тесты воспроизводимыми и независимыми от хост-машины. В этой статье мы разберём, как настроить интеграционное тестирование Go-приложений с Docker — от простых контейнеров до продвинутых сценариев с Testcontainers и CI/CD.

1. Почему Docker для интеграционного тестирования Go?

Go-приложения часто работают с PostgreSQL, Redis, MongoDB или другими сервисами. Ручная настройка этих зависимостей на каждой машине разработчика и CI-сервере — путь к ошибкам. Docker решает эту проблему:

  • Воспроизводимость — одинаковое окружение локально и в CI.
  • Изоляция — тесты не засоряют хост-систему.
  • Скорость — контейнеры запускаются за секунды.
  • Параллелизм — можно запускать несколько изолированных тестов одновременно.

Рассмотрим базовый пример: тестируем функцию, которая сохраняет данные в PostgreSQL.

2. Настройка тестового окружения с Docker Compose

Создадим docker-compose.test.yml для тестовой базы данных:

version: '3.8'
services:
  postgres_test:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: test
      POSTGRES_PASSWORD: test
      POSTGRES_DB: testdb
    ports:
      - "5433:5432"  # используем нестандартный порт, чтобы не мешать локальной БД
    tmpfs: /var/lib/postgresql/data  # данные в памяти для скорости

В Go-тесте поднимаем контейнер через os/exec или используем библиотеку testcontainers-go. Покажем оба подхода.

2.1. Ручной запуск Docker Compose из теста

// main_test.go
package main

import (
    "context"
    "fmt"
    "os/exec"
    "testing"
    "time"
)

func setupTestDB(t *testing.T) string {
    t.Helper()
    // Запускаем контейнер
    cmd := exec.Command("docker-compose", "-f", "docker-compose.test.yml", "up", "-d")
    if err := cmd.Run(); err != nil {
        t.Fatalf("failed to start docker-compose: %v", err)
    }
    // Ждём готовности PostgreSQL
    time.Sleep(3 * time.Second)
    return "postgres://test:test@localhost:5433/testdb?sslmode=disable"
}

func TestSaveUser(t *testing.T) {
    dsn := setupTestDB(t)
    db, err := sql.Open("postgres", dsn)
    if err != nil {
        t.Fatal(err)
    }
    defer db.Close()
    // ... тестовый код
}

Недостатки: ручной sleep, сложность с параллельными тестами. Лучше использовать Testcontainers.

2.2. Testcontainers для Go

Установите библиотеку:

go get github.com/testcontainers/testcontainers-go

Пример с автоматическим управлением контейнером:

// repository_test.go
package repository

import (
    "context"
    "testing"
    "github.com/testcontainers/testcontainers-go"
    "github.com/testcontainers/testcontainers-go/wait"
    "github.com/jackc/pgx/v5/pgxpool"
)

func TestUserRepository_Save(t *testing.T) {
    ctx := context.Background()

    // Запрашиваем PostgreSQL контейнер
    req := testcontainers.ContainerRequest{
        Image:        "postgres:16-alpine",
        ExposedPorts: []string{"5432/tcp"},
        Env: map[string]string{
            "POSTGRES_USER":     "test",
            "POSTGRES_PASSWORD": "test",
            "POSTGRES_DB":       "testdb",
        },
        WaitingFor: wait.ForLog("database system is ready to accept connections"),
    }

    postgres, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
        ContainerRequest: req,
        Started:          true,
    })
    if err != nil {
        t.Fatal(err)
    }
    defer postgres.Terminate(ctx) // автоматическая очистка

    // Получаем динамический порт
    port, _ := postgres.MappedPort(ctx, "5432")
    dsn := fmt.Sprintf("postgres://test:test@localhost:%s/testdb?sslmode=disable", port.Port())

    pool, _ := pgxpool.New(ctx, dsn)
    defer pool.Close()

    repo := NewUserRepository(pool)
    err = repo.Save(ctx, User{Name: "Alice", Email: "alice@example.com"})
    if err != nil {
        t.Errorf("Save() error = %v", err)
    }
}

Преимущества: нет sleep, динамические порты, параллельные тесты не конфликтуют, контейнеры удаляются автоматически.

3. Интеграционное тестирование с Redis и внешними API

Часто приложение зависит от нескольких сервисов. Testcontainers легко комбинирует контейнеры.

3.1. Тестирование кэширования с Redis

func TestCacheWithRedis(t *testing.T) {
    ctx := context.Background()
    // Запускаем Redis
    redisReq := testcontainers.ContainerRequest{
        Image:        "redis:7-alpine",
        ExposedPorts: []string{"6379/tcp"},
        WaitingFor:   wait.ForLog("Ready to accept connections"),
    }
    redisC, _ := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
        ContainerRequest: redisReq,
        Started:          true,
    })
    defer redisC.Terminate(ctx)

    redisPort, _ := redisC.MappedPort(ctx, "6379")
    rdb := redis.NewClient(&redis.Options{
        Addr: fmt.Sprintf("localhost:%s", redisPort.Port()),
    })
    defer rdb.Close()

    // Тестируем кэширование
    err := rdb.Set(ctx, "key", "value", 0).Err()
    if err != nil {
        t.Fatal(err)
    }
    val, _ := rdb.Get(ctx, "key").Result()
    if val != "value" {
        t.Errorf("expected 'value', got %s", val)
    }
}

3.2. Мокирование внешнего API с WireMock

Для тестирования HTTP-клиентов используем контейнер WireMock или MockServer:

func TestExternalAPIClient(t *testing.T) {
    ctx := context.Background()
    // Запускаем WireMock с предустановленным стабом
    wiremockReq := testcontainers.ContainerRequest{
        Image:        "wiremock/wiremock:latest",
        ExposedPorts: []string{"8080/tcp"},
        Waiting                

Блоги

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