Какие подходы и инструменты используются для написания интеграционных тестов в Go?

Ответ

Интеграционные тесты проверяют взаимодействие между несколькими компонентами системы (например, сервис и база данных). В Go для этого используются следующие подходы и инструменты:

1. Тестирование HTTP-слоя с net/http/httptest

Стандартная библиотека предоставляет пакет httptest для тестирования HTTP-хендлеров без реального запуска сервера. Он позволяет создавать моки запросов (NewRequest) и записывать ответы (NewRecorder).

Пример:

func TestGetUserHandler(t *testing.T) {
    // Создаем мок-запрос к нашему хендлеру
    req := httptest.NewRequest(http.MethodGet, "/users/1", nil)
    w := httptest.NewRecorder()

    // Предположим, что GetUserHandler - это наш http.Handler
    GetUserHandler(w, req)

    // Проверяем статус-код ответа
    if w.Code != http.StatusOK {
        t.Errorf("Expected status OK; got %v", w.Code)
    }

    // Проверяем тело ответа
    var user User
    if err := json.NewDecoder(w.Body).Decode(&user); err != nil {
        t.Fatalf("Could not decode response: %v", err)
    }

    if user.ID != 1 {
        t.Errorf("Expected user ID 1; got %d", user.ID)
    }
}

2. Работа с внешними зависимостями (БД, кэши)

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

  • Инструменты: Библиотека testcontainers-go сильно упрощает управление жизненным циклом Docker-контейнеров прямо из Go-кода.

3. Разделение тестов с помощью Build Tags

Интеграционные тесты обычно медленнее юнит-тестов, поэтому их часто запускают отдельно. Для этого используются build-теги.

  1. В начало файла с интеграционным тестом добавляется специальный комментарий:

    //go:build integration
    
    package my_test
  2. Запуск тестов выполняется с указанием тега:

    # Запустить только интеграционные тесты
    go test -v -tags=integration ./...
    
    # Запустить все тесты, кроме интеграционных
    go test -v ./...

Ответ 18+ 🔞

А, интеграционные тесты в Go! Ну, это когда ты проверяешь, как твои куски кода, которые по отдельности вроде не дураки, начинают друг с другом общаться. Типа, сервис пытается поговорить с базой данных, а она ему в ответ — молчание, ёпта, или хуже того, ошибку какую-нибудь. Вот чтобы такого не было, мы их и тестируем.

1. Тестирование HTTP-хендлеров с httptest

Стандартная библиотека, блядь, не лыком шита, дала нам пакет net/http/httptest. Это такая волшебная палочка, чтобы не поднимать настоящий сервер, а просто помахать перед хендлером фальшивым запросом и посмотреть, не обосрётся ли он. Создаёшь запрос-пустышку (NewRequest) и рекордер, который всё запишет (NewRecorder).

Смотри, как это выглядит:

func TestGetUserHandler(t *testing.T) {
    // На коленке слепили запрос, типа "дай мне юзера с ID 1"
    req := httptest.NewRequest(http.MethodGet, "/users/1", nil)
    w := httptest.NewRecorder()

    // Суём это всё нашему хендлеру, как котёнка под нос
    GetUserHandler(w, req)

    // А теперь проверяем, не послал ли он нас нахуй с кодом 500
    if w.Code != http.StatusOK {
        t.Errorf("Ожидали статус ОК, а получили %v", w.Code)
    }

    // Пытаемся вытащить из ответа структуру
    var user User
    if err := json.NewDecoder(w.Body).Decode(&user); err != nil {
        t.Fatalf("Ответ такой кривой, что даже json распарсить не могу: %v", err)
    }

    // Ну и наконец, тот ли юзер пришёл, или это левый чел
    if user.ID != 1 {
        t.Errorf("Ожидали юзера с ID 1, а пришёл какой-то левак с ID %d", user.ID)
    }
}

2. Базы данных, Redis и прочая внешняя хуйня

Тут уже посерьёзнее. Если тест должен тыкать в настоящую PostgreSQL или Redis, то лучший способ — поднять эту банду в Docker-контейнере на время теста. Чистое поле, никаких левых данных от прошлых запусков.

  • Инструмент в помощь: testcontainers-go. Эта штука, блядь, просто бомба. Она из кода на Go умеет запускать контейнеры, ждать, пока база поднимется, и потом аккуратно прибить всё после тестов. Красота, а не жизнь.

3. Как не запускать эти долгие тесты каждый раз

Интеграционные тесты — они же медленные, ёпта. Запускать их при каждом go test ./... — это как каждый раз перезагружать компьютер, чтобы проверить, работает ли калькулятор. Поэтому их отсекают с помощью build-тегов.

  1. В самый верх файла с интеграционными тестами пишешь заклинание:

    //go:build integration
    
    package my_test
  2. А запускаешь их только когда надо, явно указав тег:

    # Запустить ТОЛЬКО интеграционные тесты (готовься ждать)
    go test -v -tags=integration ./...
    
    # Запустить все БЫСТРЫЕ тесты (юниты), интеграционные проигнорятся
    go test -v ./...

Вот так вот, нехитрая магия, но без неё — пиши пропало, будешь полдня ждать, пока тесты прогуляются.