Что такое интеграционные тесты?go-66

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

1. Ключевые характеристики интеграционных тестов

  • Проверяют взаимодействие между компонентами (модулями, сервисами, БД)
  • Используют реальные зависимости (БД, внешние API, файловую систему)
  • Медленнее, чем unit-тесты
  • Ближе к реальному окружению, чем unit-тесты
  • Требуют настройки тестового окружения

2. Пример интеграционного теста в Go

Рассмотрим тестирование HTTP-обработчика с реальной базой данных:

// user_handler.go
package api

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

type UserHandler struct {
    DB *sql.DB
}

func (h *UserHandler) GetUser(w http.ResponseWriter, r *http.Request) {
    id := r.URL.Query().Get("id")
    var name string
    err := h.DB.QueryRow("SELECT name FROM users WHERE id = $1", id).Scan(&name)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    w.Write([]byte(name))
}

Соответствующий интеграционный тест:

// user_handler_test.go
package api_test

import (
    "database/sql"
    "net/http"
    "net/http/httptest"
    "os"
    "testing"

    _ "github.com/lib/pq" // драйвер PostgreSQL
)

func TestGetUser_Integration(t *testing.T) {
    // Настройка тестовой БД
    db, err := sql.Open("postgres", os.Getenv("TEST_DB_DSN"))
    if err != nil {
        t.Fatalf("failed to connect to test DB: %v", err)
    }
    defer db.Close()

    // Инициализация тестовых данных
    _, err = db.Exec("INSERT INTO users (id, name) VALUES ('1', 'Test User')")
    if err != nil {
        t.Fatalf("failed to insert test data: %v", err)
    }

    // Создание тестового обработчика
    handler := &UserHandler{DB: db}

    // Создание тестового запроса
    req := httptest.NewRequest("GET", "/user?id=1", nil)
    w := httptest.NewRecorder()

    // Вызов обработчика
    handler.GetUser(w, req)

    // Проверка результата
    if w.Code != http.StatusOK {
        t.Errorf("expected status 200, got %d", w.Code)
    }
    if w.Body.String() != "Test User" {
        t.Errorf("expected 'Test User', got '%s'", w.Body.String())
    }
}

3. Особенности интеграционных тестов в Go

3.1. Теги для разделения тестов

В Go принято разделять тесты с помощью build tags:

// +build integration

package api_test

Запуск только интеграционных тестов:

go test -tags=integration ./...

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

  1. Тестовая БД:

    • Используйте отдельную схему или тестовую БД
    • Очищайте данные перед/после тестов
    • Рассмотрите использование Docker для поднятия изолированных сервисов
  2. Внешние сервисы:

    • Используйте тестовые стенды
    • Рассмотрите инструменты вроде httptest.Server для мокирования API

3.3. Инструменты

  • testcontainers-go - для запуска зависимостей в Docker
  • dockertest - альтернатива testcontainers
  • httptest - для тестирования HTTP-интеграций
  • sqlmock - для тестирования SQL-запросов (гибрид unit/integration)

4. Best Practices

  1. Изолируйте тестовые данные: Каждый тест должен работать со своим набором данных
  2. Чистите за собой: Удаляйте тестовые данные после выполнения
  3. Параллелизация: Используйте t.Parallel() где возможно
  4. Помечайте тесты: Четко отделяйте интеграционные тесты от unit-тестов
  5. Логируйте: Добавляйте достаточное количество логов для диагностики

5. Отличия от unit-тестов

Характеристика Unit-тесты Интеграционные тесты
Объем тестирования Один модуль Взаимодействие модулей
Зависимости Моки/стабы Реальные/тестовые реализации
Скорость выполнения Очень быстрые Медленные
Надежность Высокая Может зависеть от внешних факторов
Частота выполнения При каждом коммите Перед релизом/ночью

Резюмируем

Интеграционные тесты в Go — это важный слой тестирования, который:

  • Проверяет взаимодействие компонентов
  • Требует настройки тестового окружения
  • Использует реальные или приближенные к реальным зависимости
  • Дополняет unit-тесты, обеспечивая более полное покрытие

Правильно выстроенный набор интеграционных тестов значительно повышает надежность системы и помогает выявлять проблемы, которые невозможно обнаружить unit-тестами.