Интеграционное тестирование Go с Docker: от А до Я

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

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

Начать курс

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

Интеграционное тестирование — критически важный этап разработки, особенно в мире микросервисов и распределенных систем. Go (Golang) в сочетании с Docker предоставляет мощные и элегантные инструменты для создания надежных интеграционных тестов. В этой статье мы разберем, как настроить окружение, написать тесты для взаимодействия с базами данных и внешними сервисами, используя Docker, testcontainers-go и стандартную библиотеку testing.

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

Традиционные модульные тесты (unit tests) изолируют логику, но не проверяют взаимодействие с реальными зависимостями: базами данных, очередями, API. Docker решает эту проблему, позволяя:

  • Изолировать окружение: Каждый тест запускает чистый контейнер, исключая конфликты состояний.
  • Повторяемость: Одинаковое окружение на локальной машине и в CI/CD.
  • Быстрое развертывание: Контейнеры запускаются за секунды.
  • Простота очистки: После теста контейнер уничтожается.

Мы будем использовать библиотеку testcontainers-go — она предоставляет высокоуровневый API для управления жизненным циклом контейнеров прямо из кода тестов.

2. Настройка проекта и установка зависимостей

Создадим простой HTTP-сервер на Go, который сохраняет данные в PostgreSQL. Затем напишем интеграционный тест, проверяющий эндпоинт.

Структура проекта:

myapp/
├── main.go
├── go.mod
├── go.sum
├── handlers/
│   └── user.go
└── integration_test.go

Шаг 1: Инициализируем модуль и установим testcontainers:

go mod init myapp
go get github.com/testcontainers/testcontainers-go
go get github.com/testcontainers/testcontainers-go/modules/postgres
go get github.com/lib/pq
go get github.com/stretchr/testify/assert

Шаг 2: Напишем простой обработчик в handlers/user.go:

package handlers

import (
    "database/sql"
    "encoding/json"
    "net/http"
)

type User struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
}

func CreateUser(db *sql.DB) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        var user User
        if err := json.NewDecoder(r.Body).Decode(&user); err != nil {
            http.Error(w, err.Error(), http.StatusBadRequest)
            return
        }
        _, err := db.Exec("INSERT INTO users (name, age) VALUES ($1, $2)", user.Name, user.Age)
        if err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        }
        w.WriteHeader(http.StatusCreated)
        json.NewEncoder(w).Encode(user)
    }
}

3. Пишем интеграционный тест с Docker

Теперь самое интересное — тест, который поднимает PostgreSQL в Docker, создает таблицу, запускает наш сервер и проверяет его работу.

Файл integration_test.go:

package myapp

import (
    "bytes"
    "context"
    "encoding/json"
    "net/http"
    "net/http/httptest"
    "testing"

    "myapp/handlers"

    "github.com/lib/pq"
    "github.com/stretchr/testify/assert"
    "github.com/testcontainers/testcontainers-go"
    "github.com/testcontainers/testcontainers-go/modules/postgres"
    "github.com/testcontainers/testcontainers-go/wait"
)

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

    // 1. Запускаем контейнер PostgreSQL
    pgContainer, err := postgres.RunContainer(ctx,
        testcontainers.WithImage("postgres:15-alpine"),
        postgres.WithDatabase("testdb"),
        postgres.WithUsername("test"),
        postgres.WithPassword("test"),
        testcontainers.WithWaitStrategy(
            wait.ForLog("database system is ready to accept connections").
                WithOccurrence(2).
                WithStartupTimeout(60),
        ),
    )
    assert.NoError(t, err)
    defer pgContainer.Terminate(ctx)

    // 2. Получаем строку подключения
    connStr, err := pgContainer.ConnectionString(ctx, "sslmode=disable")
    assert.NoError(t, err)

    // 3. Подключаемся к БД
    db, err := sql.Open("postgres", connStr)
    assert.NoError(t, err)
    defer db.Close()

    // 4. Создаем таблицу
    _, err = db.Exec(`CREATE TABLE IF NOT EXISTS users (
        id SERIAL PRIMARY KEY,
        name TEXT NOT NULL,
        age INTEGER NOT NULL
    )`)
    assert.NoError(t, err)

    // 5. Настраиваем HTTP-сервер
    handler := handlers.CreateUser(db)
    server := httptest.NewServer(handler)
    defer server.Close()

    // 6. Отправляем тестовый запрос
    user := handlers.User{Name: "Alice", Age: 30}
    body, _ := json.Marshal(user)
    resp, err := http.Post(server.URL+"/users", "application/json", bytes.NewReader(body))
    assert.NoError(t, err)
    assert.Equal(t, http.StatusCreated, resp.StatusCode)

    // 7. Проверяем, что данные сохранились
    var result handlers.User
    json.NewDecoder(resp.Body).Decode(&result)
    assert.Equal(t, "Alice", result.Name)
    assert.Equal(t, 30, result.Age)

    // 8. Проверяем в БД напрямую
    var dbName string
    err = db.QueryRow("SELECT name FROM users WHERE name = $1", "Alice").Scan(&dbName)
    assert.NoError(t, err)
    assert.Equal(t, "Alice", dbName)
}

Разбор ключевых моментов:

  • postgres.RunContainer — автоматически скачивает образ (если его нет) и запускает контейнер.
  • wait.ForLog — ожидает готовности БД, анализируя логи контейнера.
  • httptest.NewServer — создает локальный HTTP-сервер для тестирования без открытия портов.
  • Контейнер уничтожается автоматически через defer.

4. Продвинутые техники: Testcontainers и Docker Compose

Для сложных сценариев (несколько сервисов, очереди, Redis) используйте docker-compose. Testcontainers умеет работать с ним напрямую:

import "github.com/testcontainers/testcontainers-go/compose"

func TestWithDockerCompose(t *testing.T) {
    compose, err := compose.NewDockerCompose("docker-compose.yml")                

Блоги

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