Тестирование API в Postman

Кнопка на странице — лишь витрина. Настоящие данные ходят по HTTP, и тестировать их можно (и нужно) отдельно от интерфейса.

API (Application Programming Interface) — способ, которым одна программа обращается к другой. В веб-разработке это чаще всего HTTP-запросы к серверу: браузер просит данные, сервер отвечает JSON. Postman — инструмент, который позволяет отправлять такие запросы вручную, складывать их в коллекции и автоматически проверять ответы.

Зачем тестировать API отдельно от интерфейса

Новичку кажется, что если кнопка работает — значит, работает всё. На самом деле интерфейс — это тонкий слой поверх API, и тестирование только через него оставляет огромные слепые зоны.

  • Раньше. Backend обычно готов на неделю-две раньше фронтенда. Ждать интерфейс, чтобы начать тестировать, — значит терять эти недели и потом чинить всё в спешке.
  • Глубже. Через форму нельзя отправить возраст -5: браузер не даст. А злоумышленник или сломанный клиент — отправит. Валидацию на сервере можно проверить только напрямую через API.
  • Стабильнее. UI-тесты падают от переехавшей кнопки. API-тесты падают, только когда действительно сломался контракт.
  • Точнее в диагностике. Когда UI-тест красный, непонятно, кто виноват. Когда красный API-тест, виноват сервер — и это уже половина расследования.

Именно поэтому в пирамиде тестирования API-уровень стоит посередине: он дешевле и быстрее, чем UI, и покрывает бизнес-логику, до которой юнит-тесты не дотягиваются.

Анатомия HTTP-запроса

Любой запрос состоит из четырёх частей: метод, URL (с query-параметрами), заголовки и тело.

POST https://api.shop.dev/v1/orders?notify=true
Content-Type: application/json
Authorization: Bearer eyJhbGciOi...

{ "product_id": 42, "quantity": 2 }

Методы описывают намерение. Их путают постоянно, поэтому запомните таблицу:

МетодЧто делаетМеняет данные?Идемпотентен?
GETПолучить данныеНетДа
POSTСоздать новую сущностьДаНет
PUTЗаменить сущность целикомДаДа
PATCHИзменить часть полейДаОбычно да
DELETEУдалитьДаДа

Идемпотентность — повторный одинаковый запрос не меняет результат. Два одинаковых PUT оставят ресурс в том же состоянии, а два одинаковых POST создадут два заказа. Отсюда классический баг: пользователь дважды нажал «Оплатить» — и списалось дважды. Проверять двойную отправку — обязательный пункт чек-листа.

Коды ответов: язык, на котором сервер говорит с вами

Первая цифра кода задаёт класс: 2xx — успех, 3xx — перенаправление, 4xx — ошибка клиента (вы прислали что-то не то), 5xx — ошибка сервера (сломался он). Уже одно это делит баги пополам.

КодЗначениеЧто проверять тестировщику
200 OKУспехЧто в теле действительно те данные: 200 с пустым или неверным телом — тоже баг
201 CreatedСущность созданаДолжен вернуться id созданного объекта (часто ещё заголовок Location)
204 No ContentУспех, тела нетТипичный ответ на DELETE. Тело обязано быть пустым
400 Bad RequestЗапрос невалиденБитый JSON, отсутствует обязательное поле
401 UnauthorizedВы не представилисьНет токена или он протух
403 ForbiddenВы представились, но вам нельзяПрава доступа. Золотая жила багов: обычный пользователь редактирует чужой заказ
404 Not FoundРесурса нетОтличайте «нет объекта» от «неверный URL»
409 ConflictКонфликт состоянияПовторная регистрация того же email
422 Unprocessable EntityJSON понят, но данные бессмысленныВозраст -5, дата в прошлом
429 Too Many RequestsСлишком частоRate limit — проверяйте, что он вообще есть
500 Internal Server ErrorСервер упалВсегда баг. Даже на самый дикий ввод сервер обязан ответить 400/422, а не 500

Разница 401 и 403 — любимый вопрос на собеседовании. Мнемоника: 401 — «я тебя не знаю», 403 — «я тебя знаю, и тебе нельзя».

Коллекции, окружения и переменные

Один запрос в Postman — это уже полезно. Но реальная сила начинается, когда вы собираете коллекцию: папку запросов, которая описывает целый сценарий («регистрация → логин → создание заказа → отмена заказа»). Коллекцию можно передать коллеге, положить в git и запустить целиком.

Чтобы одна и та же коллекция работала и на тестовом стенде, и на препроде, адреса и токены не пишут в запросах напрямую. Их выносят в переменные, а наборы переменных — в окружения (environments).

Окружение "stage"           Окружение "dev"
  baseUrl = https://api.stage.shop.dev    baseUrl = http://localhost:8000
  userEmail = qa@shop.dev                 userEmail = qa@shop.dev
  token   = (заполняется скриптом)        token   = (заполняется скриптом)

Запрос в коллекции:  GET {{baseUrl}}/v1/orders/{{orderId}}

Переключили окружение в выпадашке — вся коллекция поехала на другой стенд, ни один запрос править не пришлось. Уровни переменных, от узкого к широкому: local (внутри одного прогона) → environmentcollectionglobal. При совпадении имён побеждает более узкий.

Важное правило безопасности: боевые токены и пароли не место в коллекции, которую вы коммитите в репозиторий. Для них в Postman есть тип переменной secret, а в CI — переменные окружения.

Проверки: вкладка Tests

Глазами сверять ответы можно ровно до пятого запроса. Дальше нужны автоматические проверки. В Postman они пишутся на JavaScript во вкладке Scripts → Post-response (в старых версиях — Tests) и выполняются сразу после получения ответа.

// 1. Статус-код
pm.test("Статус 201", function () {
    pm.response.to.have.status(201);
});

// 2. Время ответа
pm.test("Отвечает быстрее 800 мс", function () {
    pm.expect(pm.response.responseTime).to.be.below(800);
});

// 3. Содержимое тела
pm.test("Заказ создан с нужным количеством", function () {
    const body = pm.response.json();
    pm.expect(body).to.have.property("id");
    pm.expect(body.quantity).to.eql(2);
    pm.expect(body.status).to.eql("created");
});

// 4. Сохраняем id для следующих запросов коллекции
const body = pm.response.json();
pm.environment.set("orderId", body.id);

Результат в панели Test Results:

PASS  Статус 201
PASS  Отвечает быстрее 800 мс
PASS  Заказ создан с нужным количеством

Последняя строка примера — самая интересная. pm.environment.set кладёт id только что созданного заказа в переменную, и следующий запрос коллекции сможет обратиться к {{orderId}}. Так из отдельных запросов собирается сквозной сценарий: залогинились → сохранили токен → создали заказ → сохранили id → отменили заказ по этому id.

Зеркальный механизм — Pre-request-скрипт: он выполняется до отправки и удобен, чтобы сгенерировать уникальный email или посчитать текущую дату.

Как это работает

Postman — это, по сути, обычный HTTP-клиент с приятным интерфейсом. Когда вы жмёте Send, происходит следующее:

  1. Подставляются переменные: {{baseUrl}} заменяется на строку из активного окружения.
  2. Выполняется pre-request-скрипт (если есть) в изолированной JS-песочнице.
  3. Формируется и отправляется настоящий HTTP-запрос — точно такой же, какой послал бы браузер или мобильное приложение.
  4. Приходит ответ; запускается post-response-скрипт, и его pm.test(...) дают зелёные или красные строки.

Отсюда важное следствие: Postman не «эмулирует» приложение, он говорит с сервером на том же языке. Если запрос из Postman отработал, а из браузера — нет, значит, дело не в сервере, а в том, что именно шлёт фронтенд. Это первый шаг к разделению багов фронта и бэка, о котором пойдёт речь в следующем уроке.

Коллекцию целиком гоняет Collection Runner, а в CI ту же коллекцию запускает консольная утилита Newman — так ручные проверки бесплатно превращаются в автотесты на каждый коммит.

Частые ошибки

  • Проверять только статус-код. 200 OK с телом {"orders": []}, когда заказы есть, — зелёный тест и настоящий баг. Всегда проверяйте содержимое.
  • Тестировать только happy path. Ценность API-тестирования — в негативных кейсах: пустое тело, лишние поля, чужой id, строка вместо числа, гигантский текст, отсутствующий токен.
  • Хардкод токена в запросе. Через сутки он протухнет, и вся коллекция покраснеет. Получайте токен первым запросом и кладите в переменную.
  • Тесты, зависящие от порядка и данных. Кейс, который проходит только на «свежей» базе или только если запущен третьим, будет мигать в CI и обесценит весь набор. Создавайте нужные данные внутри сценария и удаляйте за собой.
  • Забыть Content-Type: application/json. Сервер получит тело, но не поймёт формат и ответит 400 или 415 — и вы полдня будете искать баг там, где его нет.
  • Не отличать 5xx от 4xx. 400 на кривой ввод — правильное поведение, это не баг. 500 на кривой ввод — баг всегда.
  • Гонять коллекцию по проду. Особенно POST и DELETE. Сначала проверьте, какое окружение выбрано в выпадающем списке, — это дисциплина, которая однажды спасёт вам карьеру.

Итоги

  • API-тестирование начинается раньше UI, достаёт до серверной валидации и точнее указывает на виновника.
  • Метод описывает намерение; помните про идемпотентность: два POST = две сущности.
  • Код ответа — первая развилка: 4xx — виноват клиент, 5xx — всегда баг сервера. 401403.
  • Коллекция + окружения + переменные {{baseUrl}}, {{token}} = один набор запросов на все стенды.
  • Вкладка Tests превращает ручные проверки в автоматические: статус, время, тело, сохранение данных для следующего шага.
  • Проверяйте не только счастливый путь: негативные кейсы — главная ценность уровня API.
Проверьте себя
1. Вы отправили в API возраст -5. Сервер ответил 500 Internal Server Error. Это баг?
AНет: данные некорректные, значит ошибка ожидаема
BНет: 500 — это нормальная реакция на невалидный ввод
CДа: на некорректные данные сервер обязан отвечать 400 или 422, а не падать
DДа, но только если такой ввод возможен через интерфейс
2. Пользователь дважды нажал «Оплатить», и запрос POST /orders ушёл дважды. Чего стоит ожидать?
AНичего: POST идемпотентен, второй запрос будет проигнорирован
BСоздастся два заказа — POST не идемпотентен, и это классический баг
CСервер обязан вернуть 429 Too Many Requests
DВторой запрос вернёт 304 Not Modified