Ответ
Тестирование HTTP-взаимодействий в Go можно разделить на две основные категории: тестирование сервера (ваших хендлеров) и тестирование клиента (кода, который обращается к внешним API).
1. Модульное тестирование HTTP-серверов (хендлеров)
Для тестирования хендлеров в изоляции, без запуска реального веб-сервера, используется стандартный пакет net/http/httptest.
httptest.NewRequest(): Создает mock-запрос (*http.Request) для передачи в ваш хендлер.httptest.NewRecorder(): Создает mock-объектhttp.ResponseWriter, который записывает код ответа, заголовки и тело, чтобы вы могли их проверить.
package main
import (
"net/http"
"net/http/httptest"
"testing"
)
// Тестируемый хендлер
func MyHandler(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("OK"))
}
func TestMyHandler(t *testing.T) {
// 1. Создаем запрос к нашему хендлеру
req := httptest.NewRequest(http.MethodGet, "/test", nil)
// 2. Создаем ResponseRecorder для записи ответа
rr := httptest.NewRecorder()
// 3. Вызываем хендлер напрямую
MyHandler(rr, req)
// 4. Проверяем результат
if status := rr.Code; status != http.StatusOK {
t.Errorf("handler returned wrong status code: got %v want %v", status, http.StatusOK)
}
expected := `OK`
if rr.Body.String() != expected {
t.Errorf("handler returned unexpected body: got %v want %v", rr.Body.String(), expected)
}
}
2. Модульное тестирование HTTP-клиентов
Когда ваш код делает запросы к внешним сервисам, тесты не должны зависеть от доступности этих сервисов. Для этого используется httptest.NewServer, который запускает настоящий, но локальный и временный HTTP-сервер для ваших тестов.
package main
import (
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
)
// Функция, которую мы тестируем (она делает внешний запрос)
func GetUserData(apiURL string) (string, error) {
resp, err := http.Get(apiURL + "/users/1")
if err != nil {
return "", err
}
defer resp.Body.Close()
body, _ := ioutil.ReadAll(resp.Body)
return string(body), nil
}
func TestGetUserData(t *testing.T) {
// 1. Создаем тестовый сервер
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Проверяем, что клиент пришел по правильному пути
if r.URL.Path != "/users/1" {
t.Errorf("Expected to request '/users/1', got '%s'", r.URL.Path)
}
// Отдаем заранее подготовленный ответ
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{"id": 1, "name": "John Doe"}`))
}))
defer server.Close() // Важно закрыть сервер после теста
// 2. Вызываем нашу функцию, передав ей URL тестового сервера
body, err := GetUserData(server.URL)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
// 3. Проверяем, что функция правильно обработала ответ
expected := `{"id": 1, "name": "John Doe"}`
if body != expected {
t.Errorf("unexpected body: got %s want %s", body, expected)
}
}
3. Ручное и интеграционное тестирование
Для проверки работающего приложения используются инструменты вроде Postman, Insomnia или консольной утилиты curl. Они позволяют отправлять реальные запросы к запущенному приложению и анализировать ответы.
Ключевые аспекты для проверки:
- Код состояния HTTP (Status Code):
200 OK,404 Not Found,500 Internal Server Errorи т.д. - Заголовки (Headers):
Content-Type,Authorizationи другие. - Тело ответа (Response Body): Корректность данных в формате JSON, XML или другом.
- Обработка ошибок: Как система ведет себя при неверных входных данных.
- Время ответа: Убедиться, что запросы выполняются за приемлемое время.
Ответ 18+ 🔞
А, тестирование HTTP в Go, говоришь? Ну это ж классика, блядь, как борщ со сметаной! Слушай сюда, сейчас разложу по полочкам, а то народ иногда путается, как слепой кот в сортире.
Вот смотри, вся эта движуха делится на две большие, блядь, категории, как будто ты в магазине: одна полка — это твой сервер (хендлеры, которые запросы жрут), а вторая — клиент (который сам на внешние API лает). И для каждой есть свои, мать их, инструменты в стандартной библиотеке, не надо никаких левых фреймворков!
Первое — тестируем хендлеры, не поднимая весь сервер.
Представь, что тебе нужно проверить одну конкретную функцию, которая http.ResponseWriter и http.Request принимает. Запускать для этого целое приложение — это как из пушки по воробьям, ёпта. Нафиг надо? Для этого есть net/http/httptest.
Вот смотри, как это просто, в рот меня чих-пых:
package main
import (
"net/http"
"net/http/httptest"
"testing"
)
// Допустим, у нас есть вот такой простейший хендлер
func MyHandler(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("OK"))
}
func TestMyHandler(t *testing.T) {
// 1. Лепим фейковый запрос. Словно куклу подсовываем.
req := httptest.NewRequest(http.MethodGet, "/test", nil)
// 2. Создаём подставного писаку (ResponseRecorder), который всё запишет.
rr := httptest.NewRecorder()
// 3. Прямо как в бою, вызываем хендлер!
MyHandler(rr, req)
// 4. А теперь проверяем, не обосрался ли он.
if status := rr.Code; status != http.StatusOK {
t.Errorf("handler returned wrong status code: got %v want %v", status, http.StatusOK)
}
expected := `OK`
if rr.Body.String() != expected {
t.Errorf("handler returned unexpected body: got %v want %v", rr.Body.String(), expected)
}
}
Видишь? Никакой магии. Создали бутафорский запрос, подсунули его хендлеру вместе с подсадной уткой для записи ответа, и потом просто смотрим, что он нам наваял. Всё изолированно, быстро, красиво. Не хуй собачий!
Второе — тестируем клиента, который куда-то ходит.
А вот это, блядь, уже интереснее. Твой код, допустим, стучится к какому-то внешнему API за данными. И если в тестах он будет реально туда лезть — это пиздец, а не тесты. Сеть может лежать, API может сдохнуть, лимиты кончатся... Да и вообще, тесты не должны от внешнего мира зависеть, это же аксиома, ёпта!
Что делаем? Поднимаем свой, локальный, временный сервачок прямо в тесте! И он будет отдавать именно те ответы, которые мы ему пропишем. Вообще красота!
package main
import (
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
)
// Функция, которая ходит за данными пользователя
func GetUserData(apiURL string) (string, error) {
resp, err := http.Get(apiURL + "/users/1")
if err != nil {
return "", err
}
defer resp.Body.Close()
body, _ := ioutil.ReadAll(resp.Body)
return string(body), nil
}
func TestGetUserData(t *testing.T) {
// 1. Вжух! И вот он, наш личный тестовый сервер!
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Можем даже проверить, правильно ли клиент путь запросил
if r.URL.Path != "/users/1" {
t.Errorf("Expected to request '/users/1', got '%s'", r.URL.Path)
}
// А здесь отдаём тот ответ, который нам для теста нужен
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{"id": 1, "name": "John Doe"}`))
}))
defer server.Close() // Это ОБЯЗАТЕЛЬНО, иначе порты повиснут, блядь!
// 2. Дёргаем нашу функцию, но кидаем ей адрес нашего подставного сервера
body, err := GetUserData(server.URL)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
// 3. Сверяем, что она нам принесла
expected := `{"id": 1, "name": "John Doe"}`
if body != expected {
t.Errorf("unexpected body: got %s want %s", body, expected)
}
}
Вот и вся магия, блядь! Сервер создали, он отдал что надо, функция отработала, мы проверили. Никаких зависимостей от внешнего мира. Идиллическая картина, ёбана!
Ну и конечно, ручное тестирование.
Это когда уже всё собрано и нужно ткнуть пальцем, жив ли сервис. Тут уже в ход идут Postman, curl или что там у тебя любимое. Просто дёргаешь эндпоинты и смотришь, не несёт ли он хуйню в ответе.
На что вообще смотреть нужно, когда тестируешь HTTP-взаимодействия:
- Статус-код, ёпта!
200,404,500— это основа основ. Если ожидал201 Created, а получил400 Bad Request— это повод задуматься, а не обосрался ли твой код. - Заголовки (Headers). Особенно
Content-Type. Отправил JSON, а в заголовкахtext/plain— пидарас, извини. - Тело ответа (Response Body). Данные должны быть в правильном формате (JSON, XML) и структуре. Если ждёшь
{"name": "Vasya"}, а приходит{"username": "Petya"}, то что-то пошло не так, блядь. - Обработка ошибок. А что будет, если отправить полную хуйню вместо JSON? Упадёт с паникой или красиво ответит
400с описанием? Это важно! - Скорость. Ну это уже для перфекционистов, но если запрос выполняется 10 секунд, то это тоже, мягко говоря, не айс.
Вот так вот, коротко и по делу. Главное — разделяй тесты на изолированные модульные и интеграционные, где поднимаешь части системы. И не усложняй там, где не надо, а то сам потом в своём коде разбираться не сможешь, как мартышка в апельсинах!