Как правильно тестировать взаимодействие между микросервисами? Нужно ли писать тесты для чужого сервиса?

Ответ

Прямое тестирование внутренней логики чужого микросервиса — это антипаттерн. Это зона ответственности команды, которая его разрабатывает. Наша задача — тестировать взаимодействие с ним, то есть проверять, что наш сервис корректно отправляет запросы и обрабатывает ответы в соответствии с оговоренным контрактом (API).

Существует несколько подходов:

  1. Интеграционные тесты с моками (заглушками) Это самый частый подход. Мы не используем реальный соседний сервис, а поднимаем его легковесную имитацию (мок) во время теста. В Go для этого отлично подходит пакет net/http/httptest.

    • Что мы проверяем:
      • Наш сервис правильно формирует HTTP-запрос (URL, заголовки, тело).
      • Наш сервис корректно парсит успешный ответ.
      • Наш сервис правильно обрабатывает ошибки (например, 404 или 500).

    Пример (тестируем наш API-клиент):

    package main
    
    import (
        "encoding/json"
        "net/http"
        "net/http/httptest"
        "testing"
    )
    
    // Структура пользователя, которую мы ожидаем от сервиса B
    type User struct {
        ID   int    `json:"id"`
        Name string `json:"name"`
    }
    
    // Клиент для взаимодействия с сервисом B
    type APIClient struct {
        BaseURL string
        Client  *http.Client
    }
    
    // Метод нашего клиента, который мы хотим протестировать
    func (c *APIClient) GetUser(id int) (*User, error) {
        // ... логика похода в сервис B
        // В данном примере она не важна, так как мы тестируем взаимодействие
        // с мок-сервером, который имитирует сервис B.
        return nil, nil // Реальная логика здесь не нужна для примера
    }
    
    func TestGetUserFromServiceB(t *testing.T) {
        // 1. Создаем тестовый сервер, который будет имитировать сервис B
        server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            // Проверяем, что наш клиент пришел по правильному URL
            if r.URL.Path != "/users/1" {
                t.Errorf("Expected to request '/users/1', got %s", r.URL.Path)
            }
            w.WriteHeader(http.StatusOK)
            // Отдаем заранее подготовленный ответ
            json.NewEncoder(w).Encode(User{ID: 1, Name: "Alice"})
        }))
        defer server.Close()
    
        // 2. Создаем экземпляр нашего клиента, указывая ему адрес мок-сервера
        // В реальном коде здесь будет метод вроде NewAPIClient(server.URL)
        // client := NewAPIClient(server.URL)
    
        // 3. Выполняем запрос и проверяем результат
        // user, err := client.GetUser(1)
    
        // ... тут идут проверки (asserts), что user.Name == "Alice" и err == nil
    }
  2. Контрактные тесты Более продвинутый подход. Производитель (сервис, который предоставляет API) и потребитель (наш сервис) договариваются о "контракте". Контракт описывает структуру запросов и ответов. Тесты автоматически проверяют, что и производитель, и потребитель соответствуют этому контракту. Инструменты: Pact, Pact-Go.

  3. End-to-End (E2E) тесты Эти тесты запускаются на полностью развернутом окружении (например, staging), где все микросервисы работают по-настоящему. Они проверяют сквозные пользовательские сценарии. Это самые дорогие и медленные тесты, но они единственные, кто проверяет систему в целом.

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

Ответ 18+ 🔞

Да ты послушай, что за дичь творят некоторые разработчики! Прямо как будто у них мозги в жопе, прости Господи. Решили они, понимаешь, тестировать внутреннюю логику чужого микросервиса. Ну это ж пиздец полный, ёпта! Это как прийти к соседу и начать проверять, правильно ли он свой телевизор настраивает. Не твоё, блядь, собачье дело!

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

Вот как это делается, если ты не совсем конченый:

  1. Интеграционные тесты с заглушками (моками) Самый адекватный и частый способ. Мы не дергаем реальный сервис, а поднимаем его легковесную подделку прямо в тесте. В Go для этого есть net/http/httptest — просто красота, а не пакет.

    • Что мы по факту проверяем:
      • Формируем ли мы запрос как положено (куда стучимся, что в тело пихаем).
      • Умеем ли разобрать нормальный ответ.
      • Не обоссываемся ли, если сосед нам вернул ошибку (типа 404 или 500).

    Смотри, как это выглядит (тестируем нашего API-клиента):

    package main
    
    import (
        "encoding/json"
        "net/http"
        "net/http/httptest"
        "testing"
    )
    
    // Структура пользователя, которую мы ожидаем от сервиса B
    type User struct {
        ID   int    `json:"id"`
        Name string `json:"name"`
    }
    
    // Клиент для взаимодействия с сервисом B
    type APIClient struct {
        BaseURL string
        Client  *http.Client
    }
    
    // Метод нашего клиента, который мы хотим протестировать
    func (c *APIClient) GetUser(id int) (*User, error) {
        // ... логика похода в сервис B
        // В данном примере она не важна, так как мы тестируем взаимодействие
        // с мок-сервером, который имитирует сервис B.
        return nil, nil // Реальная логика здесь не нужна для примера
    }
    
    func TestGetUserFromServiceB(t *testing.T) {
        // 1. Создаем тестовый сервер, который будет имитировать сервис B
        server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            // Проверяем, что наш клиент пришел по правильному URL
            if r.URL.Path != "/users/1" {
                t.Errorf("Expected to request '/users/1', got %s", r.URL.Path)
            }
            w.WriteHeader(http.StatusOK)
            // Отдаем заранее подготовленный ответ
            json.NewEncoder(w).Encode(User{ID: 1, Name: "Alice"})
        }))
        defer server.Close()
    
        // 2. Создаем экземпляр нашего клиента, указывая ему адрес мок-сервера
        // В реальном коде здесь будет метод вроде NewAPIClient(server.URL)
        // client := NewAPIClient(server.URL)
    
        // 3. Выполняем запрос и проверяем результат
        // user, err := client.GetUser(1)
    
        // ... тут идут проверки (asserts), что user.Name == "Alice" и err == nil
    }
  2. Контрактные тесты Это уже для продвинутых, у кого терпения ебать. Тут производитель API и мы, потребители, договариваемся на бумажке (в коде), как всё должно выглядеть. А потом специальные тесты (типа Pact) автоматом проверяют, что никто не отклонился от договоренностей. Серьёзная штука, но и возни с ней — овердохуища.

  3. End-to-End (E2E) тесты А это уже тяжёлая артиллерия. Запускается на почти боевом стенде, где все сервисы живые. Проверяет весь путь от нажатия кнопки до результата. Тесты эти — медленные, дорогие и капризные как мартышлюшка. Но без них иногда никуда, потому что только они покажут, что вся эта конструкция из кучи микросервисов в целом ещё хоть как-то работает.

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