Тестирование API часто сводится к одному: отправить запрос и убедиться, что сервер ответил 200. На собеседовании этого хватает на пару минут, а в проде именно здесь всплывает то, чего статус-код не ловит: чужой заказ в ответе, молчаливый 500 вместо ошибки валидации, товар, который не списался со склада.
Разберём 20 базовых проверок, которые отличают QA, тестирующего API, от того, кто просто дёргает ручки. Пять групп — успешный ответ, входные данные, ошибки, доступ и состояние системы — на одном сквозном сценарии, с примерами на curl и в Postman.
Статья поможет начинающему QA выстроить последовательность проверки, а специалисту с опытом — быстро свериться с основными сценариями и понять, куда расширять набор тестов.
Чтобы проверки были из реальной работы, а не из теории, материал сверили с тем, кто тестирует API каждый день:

Александр Мужев
Ведущий QA-инженер полного цикла в «Альфа-Деньгах», эксперт программ Нетологии «Инженер по тестированию» и «Фулстек-разработчик на Python»
Короткий словарь
Ниже встретятся термины из документации, Postman и примеров кода. Коротко разберём их до основной части.
API — способ, с помощью которого программы обмениваются запросами и ответами по заранее установленным правилам.
Контракт API — описание того, какие запросы принимает API и какие ответы должен возвращать: методы, адреса, поля, типы данных, статусы и ошибки.
Эндпоинт — конкретная операция API: HTTP-метод плюс адрес, например, POST /orders.
OpenAPI Specification — стандарт машиночитаемого описания API. Документ в формате YAML или JSON перечисляет эндпоинты, параметры, тела запросов, схемы данных и возможные ответы. Числа 3.2.0 обозначают версию стандарта, а не версию тестируемого API.
Swagger UI — инструмент, который превращает OpenAPI-документ в интерактивную веб-страницу: на ней можно изучить операции и отправить пробный запрос.
HTTP-метод — команда, которая показывает, что сделать с данными по указанному адресу. GET получает данные, POST создаёт, PATCH частично изменяет, DELETE удаляет.
JSON — текстовый формат передачи данных. В нём объект записывают как набор пар «ключ — значение».
Схема, или JSON Schema, — набор машиночитаемых правил для JSON: какие поля обязательны, какие у них типы и какие значения допустимы.
Валидация — проверка данных по правилам. Сервер валидирует входной запрос, а тест может валидировать ответ.
Токен — строка, по которой сервер узнаёт пользователя или приложение. Запись Bearer <token> в заголовке Authorization означает, что запрос передаёт Bearer-токен.
2xx, 4xx и 5xx — классы HTTP-статусов: успешные ответы, ошибки запроса клиента и ошибки сервера.
Состояние системы — данные после операции. Побочный эффект — изменение за пределами текущего ответа, например, уменьшение остатка товара.
Идемпотентность — свойство операции, при котором повтор одного и того же запроса не меняет итоговый эффект.
RFC — опубликованный технический документ со стандартом или правилами работы интернет-протоколов. Номер после аббревиатуры указывает на конкретный документ, например, RFC 9110 описывает семантику HTTP.
OWASP — некоммерческое сообщество, которое публикует рекомендации и списки распространённых уязвимостей веб-приложений и API.
UUID — строковый уникальный идентификатор, например, 550e8400-e29b-41d4-a716-446655440000.
Что именно тестирует QA в API
QA сравнивает фактическое поведение API с ожидаемым. Ожидаемый результат — это не просто ответ без ошибки. Запрос должен соответствовать техническому контракту, данные — бизнес-правилам, а система после операции — перейти в правильное состояние.
Из чего состоит запрос
Возьмём создание заказа через POST /orders. На уровне API запрос складывается из нескольких частей:
Путь и HTTP-метод. Путь
/ordersуказывает на ресурс, а методPOST— на действие с ним. Тот же путь с методомGETозначает уже другую операцию: получить список заказов.Параметры. Уточняют запрос: задают фильтр, сортировку, номер страницы или конкретный ID.
Заголовки. Передают служебную информацию: формат данных или токен пользователя в
Authorization.Тело запроса. Содержит данные для создания или изменения ресурса: товары, количество и адрес доставки.
Все части запроса можно описать в OpenAPI-документе: перечислить адреса, методы, параметры, тело и возможные ответы. Swagger UI читает этот документ и показывает его как интерактивную справку, где можно изучить операции и отправить пробный запрос.
Что приходит в ответ
После обработки запроса сервер возвращает ответ. В нём проверяем три основные части:
Статус-код. Показывает результат обработки запроса: например, ресурс создан, данные не найдены или клиент передал некорректные параметры.
Заголовки. Передают метаданные ответа: формат содержимого, правила кеширования, ограничения по запросам и другую служебную информацию.
Тело ответа. Содержит данные ресурса или описание ошибки, если тело предусмотрено контрактом.
Правила для HTTP-методов и статус-кодов описаны в RFC 9110. Например, GET используют для получения данных, а класс 4xx — для ошибок, связанных с запросом клиента. Код должен соответствовать фактическому результату операции.
При этом одного подходящего кода недостаточно. POST /orders может вернуть 201 и корректный JSON, хотя сумма рассчитана неверно, заказ записан на другого пользователя или товар не списался со склада.
Откуда брать ожидаемый результат
Проверять ответ нужно не исходя из здравого смысла и не по текущему поведению API. Ожидаемое поведение собирают из нескольких источников:
Контракт API: OpenAPI-документ или другая документация с методами, параметрами, схемами запросов и вариантами ответов.
Требования и критерии приёмки: пользовательские сценарии, ограничения и ожидаемый результат операции.
Бизнес-правила: расчёт суммы и скидки, права ролей, переходы между статусами, изменение остатков и связанных данных.
ISTQB CTFL 4.0.1 — программа базового уровня международной сертификации тестировщиков. Она относит к целям тестирования оценку требований, пользовательских историй, дизайна и кода, а также проверку покрытия. На практике QA проверяет два уровня: соблюдает ли API технический контракт и правильно ли операция реализует продуктовый сценарий.
Сквозной сценарий: заказ в интернет-магазине
Чтобы не собирать 20 проверок из разных примеров, возьмём условный API интернет-магазина. Пользователь добавляет товар в заказ, указывает адрес доставки и отправляет запрос. Сервер создаёт заказ, рассчитывает сумму и возвращает сохранённые данные.
API вымышленный, поэтому конкретные поля и правила заданы для примера. В реальном проекте их нужно брать из документации и требований продукта.
Какие операции будем проверять
В сценарии участвуют четыре связанные операции:
POST /orders— создать заказ.GET /orders/{id}— получить заказ по его ID.PATCH /orders/{id}— изменить часть данных заказа: количество товара или адрес доставки.DELETE /orders/{id}— удалить или отменить заказ, если это допускают правила продукта.
Семантика POST, GET и DELETE описана в RFC 9110, а PATCH — в отдельном RFC 5789. PATCH передаёт только набор изменений для существующего объекта, а не его полную новую версию.
Базовый запрос на создание заказа
Сначала отправим корректный запрос с обязательными полями и действующим токеном пользователя:
curl -X POST https://api.example.com/orders \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{ "items": [ { "product_id": 125, "quantity": 2 } ], "delivery_address": "Москва, ул. Примерная, 10" }'
Предположим, сервер возвращает такой ответ:
HTTP/1.1 201 Created Content-Type: application/json Location: /orders/8457 { "id": 8457, "user_id": 24, "items": [ { "product_id": 125, "quantity": 2, "price": 1590 } ], "delivery_address": "Москва, ул. Примерная, 10", "status": "created", "total": 3180 }
По RFC 9110 статус 201 означает, что запрос выполнен и привёл к созданию ресурса. Заголовок Location может указывать на адрес созданного заказа. Но для тестировщика это только начало проверки. Нужно разобрать сам ответ и убедиться, что заказ действительно появился в системе.
Как будем использовать этот сценарий
Сначала проверим успешный ответ, затем будем менять по одному условию: уберём обязательное поле, передадим неверный тип данных, заменим токен, обратимся к чужому заказу и повторим одну операцию несколько раз.
После POST получим заказ через GET и сравним данные. Затем изменим его через PATCH, снова запросим актуальную версию, удалим через DELETE и проверим финальное состояние. Так каждая из 20 проверок будет связана с одной системой и понятным ожидаемым результатом.

Инженер по тестированию: расширенный курс
Освоите API-тесты в Postman, автотесты на Python, нагрузочное тестирование и безопасность. За 14 месяцев — 20 проектов в портфолио и диплом о профпереподготовке.
20 проверок API
Идём от ответа одного запроса к состоянию всей системы. Сначала проверим статус, структуру и значения успешного ответа, затем — входные данные, ошибки, права доступа и результат связанных операций.
Перед каждым тестом фиксируйте три вещи: исходный запрос, ожидаемый ответ и состояние системы после операции. Для POST /orders недостаточно увидеть код из класса 2xx: заказ должен появиться в системе с правильными данными и только один раз.
Проверяем успешный запрос: 5 проверок
Отправляем базовый POST /orders с валидным токеном, существующим товаром и корректным адресом. Сервер возвращает 201 Created и объект нового заказа. Теперь последовательно проверим ответ.
Все примеры ниже рассчитаны на Postman.
Вставляйте код во вкладку Scripts → Post-response нужного запроса: такой скрипт запускается после получения ответа. Функция pm.test() создаёт отдельную проверку, pm.response даёт доступ к ответу сервера, а pm.expect() сравнивает фактическое значение с ожидаемым. Результаты появятся во вкладке Test Results.
1. Статус-код
Для создания заказа ожидаем 201. По правилам RFC 9110 этот статус означает, что запрос выполнен и привёл к созданию нового ресурса. Если контракт вашего API предусматривает 200, 202 или другой код, тест должен опираться именно на контракт.
Что проверяем:
код совпадает с документацией для
POST /orders;успешный код не скрывает ошибку в теле ответа: объект с полем
error;ответ согласован со статусом: при
201созданный ресурс существует, а заголовокLocation, если он предусмотрен контрактом, ведёт на него.
Пример проверки в Postman:
pm.test("Статус — 201 Created", () => { pm.response.to.have.status(201); }); pm.test("Location указывает на созданный заказ", () => { pm.expect(pm.response.headers.get("Location")) .to.match(/^\/orders\/\d+$/); });
2. Структура ответа
Берём схему ответа из OpenAPI-документа, в Swagger UI она обычно показана рядом с операцией. Для нашего заказа обязательны поля id, user_id, items, delivery_address, status и total. Внутри items ожидаем массив объектов с заранее определёнными свойствами.
Проверяем:
все обязательные поля присутствуют;
вложенность сохранена:
items— массив, а товар находится внутри элемента массива;названия полей не изменились и не отличаются регистром;
нет неожиданных свойств, которых нет в контракте.
Эту проверку удобно выполнять по JSON Schema. Она позволяет одним тестом проверить обязательные свойства, вложенность и типы. Но схема подтверждает только форму ответа. Правильность значений и бизнес-логику всё равно нужно проверять отдельно.
Пример проверки в Postman:
const schema = { type: "object", required: [ "id", "user_id", "items", "delivery_address", "status", "total" ], additionalProperties: false, properties: { id: { type: "integer" }, user_id: { type: "integer" }, items: { type: "array", minItems: 1 }, delivery_address: { type: "string" }, status: { type: "string" }, total: { type: "number" } } }; pm.test("Ответ соответствует схеме", () => { pm.response.to.have.jsonSchema(schema); });
3. Типы и форматы данных
В JSON число и строка — разные типы. Поэтому "id": 8457 соответствует контракту с числовым идентификатором, а "id": "8457" уже нет, даже если интерфейс одинаково показывает оба значения.
Для каждого поля сверяем:
тип:
id,quantity,priceиtotalдолжны приходить числами;формат: дата и время — в формате из контракта, например, RFC 3339 (
2026-08-19T14:30:00Z); идентификатор — число илиUUIDв зависимости от схемы;ограничения массива:
itemsне пустой и содержит объекты нужной структуры.
Отдельно проверьте поля, которых нет в нашем коротком примере, но которые обычно добавляет сервер: created_at, updated_at, номер заказа. Значение может выглядеть правильно и при этом не проходить строгую проверку формата.
Пример проверки в Postman:
const order = pm.response.json(); pm.test("Типы полей соответствуют контракту", () => { pm.expect(order.id).to.be.a("number"); pm.expect(order.user_id).to.be.a("number"); pm.expect(order.items).to.be.an("array").that.is.not.empty; pm.expect(order.items[0].quantity).to.be.a("number"); pm.expect(order.items[0].price).to.be.a("number"); pm.expect(order.total).to.be.a("number"); });
4. Значения данных
Теперь сравниваем не форму, а содержание ответа. Значения должны соответствовать запросу, авторизованному пользователю и данным системы на момент создания заказа.
В нашем сценарии проверяем, что:
product_idравен125, аquantity—2;delivery_addressсовпадает с адресом из запроса;user_idотносится к пользователю из токена, а не берётся из клиентского тела;priceсоответствует актуальной цене товара, которую использует сервер.
Сравнение только с запросом подходит не для всех полей. Клиент не передаёт id, status и итоговую стоимость. Их формирует сервер. Для таких значений нужен отдельный источник ожидаемого результата: требования, данные тестового стенда или результат следующего GET /orders/8457.
Пример проверки в Postman:
const sent = JSON.parse( pm.variables.replaceIn(pm.request.body.raw) ); const order = pm.response.json(); pm.test("Ответ содержит отправленные данные", () => { pm.expect(order.items[0].product_id) .to.eql(sent.items[0].product_id); pm.expect(order.items[0].quantity) .to.eql(sent.items[0].quantity); pm.expect(order.delivery_address) .to.eql(sent.delivery_address); });
5. Бизнес-логика ответа
Последняя проверка успешного сценария — вычисления и правила продукта.
В простом случае итоговую стоимость можно проверить как price × quantity. Но в реальном API формула может учитывать скидки, промокоды, стоимость доставки, налоги, округление и другие условия. Поэтому ожидаемый total рассчитываем не по универсальной формуле, а строго по бизнес-правилам из требований.
Проверяем:
totalрассчитан по формуле из требований, а не просто повторяет значение из запроса;statusполучил допустимое начальное значениеcreated;скидка, доставка, налоги и округление применены в правильном порядке;
ответ не содержит
password_hash, внутренних идентификаторов, чужогоemailи других данных, которые не нужны клиенту.
Последний пункт относится не только к чистоте контракта, но и к безопасности. OWASP рекомендует ограничивать ответ минимально необходимым набором полей и отдельно контролировать доступ на уровне каждого свойства.
Итог этой группы проверок: 201 и валидный JSON ещё не доказывают, что операция отработала правильно. Успешным считаем тест, в котором совпали статус, структура, типы, значения и бизнес-результат.
В реальном тесте expectedTotal рассчитываем независимо от ответа API и строго по формуле из требований. В примере ниже используем упрощённый расчёт без скидок, доставки и налогов.
Пример проверки в Postman:
const order = pm.response.json(); // Упрощённый расчёт: без скидок, доставки и налогов. const expectedTotal = order.items.reduce( (sum, item) => sum + item.price * item.quantity, 0 ); pm.test("Итоговая сумма рассчитана правильно", () => { pm.expect(order.total).to.eql(expectedTotal); pm.expect(order.status).to.eql("created"); }); pm.test("В ответе нет закрытых полей", () => { ["password_hash", "internal_id", "email"].forEach((field) => { pm.expect(order).not.to.have.property(field); }); });
Проверяем входные данные: 5 проверок
Теперь оставляем тот же POST /orders, но в каждом тесте меняем только одно условие. Если одновременно убрать адрес, передать строку вместо количества и добавить неизвестное поле, по ответу будет непонятно, какую ошибку обработал сервер.
Для негативного сценария проверяем не только код из класса 4xx. Важно, чтобы ответ объяснял причину отказа, не раскрывал внутреннее устройство сервера и не создавал заказ частично.
6. Обязательные поля
Убираем delivery_address из тела запроса. В OpenAPI и JSON Schema поля перечисляют в разделе properties, а обязательные дополнительно указывают в списке required. Поэтому само наличие delivery_address в схеме ещё не означает, что поле обязательно.
Ожидаемый результат:
сервер возвращает предусмотренный контрактом код:
400или422;ошибка указывает на
delivery_addressи объясняет, что поле обязательно;заказ не создаётся и остатки товара не меняются.
Повторяем проверку для других обязательных уровней: удаляем весь массив items, затем product_id или quantity внутри одной позиции. Вложенные поля валидируются отдельно от корневых.
Пример проверки в Postman:
const error = pm.response.json(); pm.test("Запрос без delivery_address отклонён", () => { pm.response.to.have.status(422); }); pm.test("Ошибка указывает на обязательное поле", () => { pm.expect(JSON.stringify(error)) .to.include("delivery_address"); });
7. Пустые и null-значения
Поле может присутствовать в запросе и всё равно не содержать пригодного значения. Для адреса по очереди передаём пустую строку "", строку из пробелов " " и null. В JSON null — отдельное значение, а не отсутствие свойства.
Проверяем, что API:
различает отсутствующее поле, пустую строку и
null, если это предусмотрено контрактом;не принимает строку из пробелов как полноценный адрес;
возвращает понятную ошибку с названием поля;
не подменяет невалидное значение произвольным значением по умолчанию.
Допустимость null определяется схемой и бизнес-логикой. Например, комментарий к заказу может быть необязательным, а адрес доставки для курьерского заказа — наоборот.
Пример проверки в Postman:
const error = pm.response.json(); pm.test("Пустой или null-адрес отклонён", () => { pm.response.to.have.status(422); pm.expect(JSON.stringify(error)) .to.include("delivery_address"); });
8. Тип и формат данных
Меняем тип одного значения: вместо "quantity": 2 отправляем "quantity": "2". Затем проверяем другие варианты: объект вместо строки в delivery_address, дробное число в quantity, идентификатор неверного формата в product_id.
Ожидаем, что сервер отклонит запрос, а не будет молча приводить данные к удобному типу. Автоматическое преобразование строки в число может скрыть ошибку клиента и привести к разному поведению в соседних эндпоинтах.
Для дат одного типа string недостаточно. Значение должно соответствовать формату из контракта. Если поле описано как дата и время, отдельно проверяем невозможную дату, пропущенный часовой пояс и значение не в том формате.
Пример проверки в Postman:
const sent = JSON.parse( pm.variables.replaceIn(pm.request.body.raw) ); pm.test("В тесте quantity передан строкой", () => { pm.expect(sent.items[0].quantity).to.be.a("string"); }); pm.test("Неверный тип данных отклонён", () => { pm.response.to.have.status(422); });
9. Граничные и недопустимые значения
Предположим, один товар можно добавить в количестве от 1 до 99. В JSON Schema такие границы задают ограничения minimum и maximum. Для теста берём значения на самой границе и сразу за ней:
1и99— допустимые границы;0и100— значения на один шаг за границей;-1— явно недопустимое отрицательное значение;1.5— значение правильного числового типа, но неподходящее для количества штук.
Так мы используем классы эквивалентности — группы значений, которые система должна обрабатывать одинаково. Вместо всех чисел проверяем по одному представителю каждой группы: допустимой и недопустимой. Если максимум зависит от остатка, повторяем тест для stock, stock + 1 и нулевого остатка.
Что делает скрипт: pm.request.body.raw берёт тело текущего запроса как текст, pm.variables.replaceIn() подставляет значения переменных Postman, а JSON.parse() превращает строку в объект JavaScript — структуру, из которой можно читать отдельные поля. Затем requestBody.items[0].quantity обращается к количеству в первой позиции заказа.
Пример проверки в Postman:
const requestBody = JSON.parse( pm.variables.replaceIn(pm.request.body.raw) ); const quantity = requestBody.items[0].quantity; const isValid = Number.isInteger(quantity) && quantity >= 1 && quantity <= 99; pm.test(`quantity = ${quantity} обработан правильно`, () => { if (isValid) { pm.response.to.have.status(201); } else { pm.response.to.have.status(422); } });
10. Неизвестные и лишние поля
Добавляем в запрос свойство, которого нет в контракте, например, "test_field": true. Затем пробуем передать серверное поле "status": "paid" или подменить total. Это разные риски: первое проверяет работу с дополнительными свойствами, второе — может ли клиент менять защищённые данные.
Ожидаемое поведение берём из контракта. Если API работает в строгом режиме и схема запрещает дополнительные свойства, запрос с неизвестным полем должен быть отклонён — например, с 400 Bad Request или 422 Unprocessable Content. Молча принимать неизвестные поля в таком API опасно: ошибка клиента остаётся незаметной и может привести к расхождению контрактов между сервисами.
Для серверных свойств правило строже: значение клиента не должно влиять на состояние заказа.
Проверяем:
неизвестное поле не появляется в ответе и в последующем GET-запросе;
statusостаётсяcreated, аtotalрассчитывает сервер;лишнее свойство внутри элемента
itemsобрабатывается так же предсказуемо, как корневое;ошибка не раскрывает имя таблицы, стек вызовов или фрагмент SQL.
OWASP относит возможность читать или менять недоступные клиенту свойства к нарушению авторизации на уровне отдельных свойств. Поэтому тест с лишним полем — базовая проверка безопасности.
После каждого негативного запроса выполняем GET и проверяем состояние системы. Если API вернул 400, но заказ всё же появился, обработка входных данных сломана независимо от того, насколько правильно выглядит сообщение об ошибке.
Пример проверки в Postman:
const order = pm.response.json(); pm.test("Сервер не принял клиентские поля", () => { pm.expect(order).not.to.have.property("test_field"); pm.expect(order.status).to.eql("created"); pm.expect(order.total).not.to.eql(1); });
Проверяем обработку ошибок: 3 проверки
Ответ с ошибкой — такая же часть контракта. У него должны быть предсказуемый статус, стабильная структура и безопасное содержание.
11. Корректный статус ошибки
Сначала проверяем, различает ли API причины отказа. Некорректный по формату идентификатор, отсутствующий заказ, конфликт состояния и внутренний сбой — это разные ситуации, поэтому один универсальный 400 для всех ответов мешает клиенту правильно обработать ошибку.
Для сквозного сценария можно взять такие случаи:
GET /orders/abc— идентификатор неправильного формата;GET /orders/999999— формат корректный, но заказа не существует;PATCH /orders/8457после отмены заказа — операция конфликтует с текущим состоянием;искусственно вызванный сбой зависимости — сервер не может завершить операцию.
Ожидаемые коды берём из контракта. Например, API может возвращать 400, 404, 409 и 500. Главное, чтобы одинаковые ситуации обрабатывались одинаково, а ответ не маскировал серверную ошибку под успешный 200.
Пример проверки в Postman:
const expectedStatus = 404; // Для другого сценария замените 404 на код из контракта. pm.test("API вернул ожидаемый статус ошибки", () => { pm.response.to.have.status(expectedStatus); });
12. Тело ошибки
Статус сообщает класс проблемы, но не объясняет, какое поле исправить или почему операция запрещена. Поэтому проверяем тело ошибки так же, как успешный ответ: обязательные свойства, типы, значения и стабильность структуры.
В качестве ориентира можно использовать Problem Details из RFC 9457 — стандартный формат JSON-объекта с ошибкой. В нём предусмотрены поля type, title, status, detail и instance. API не обязан применять именно этот формат, но собственный объект ошибки тоже должен быть единообразным.
Проверяем, что:
машиночитаемый код или тип позволяет клиенту отличить одну ошибку от другой;
сообщение помогает исправить запрос, а не просто сообщает «что-то пошло не так»;
для ошибки валидации указано проблемное поле, например,
delivery_address;статус в теле, если он есть, совпадает с HTTP-статусом ответа.
Пример проверки в Postman:
const problem = pm.response.json(); pm.test("Ошибка соответствует контракту", () => { pm.expect(pm.response.headers.get("Content-Type")) .to.include("application/problem+json"); pm.expect(problem).to.include.all.keys( "type", "title", "status", "detail" ); pm.expect(problem.status).to.eql(pm.response.code); });
13. Безопасность ответа при ошибке
Сбой может произойти на разных уровнях: в приложении, базе данных, внешнем сервисе или на прокси-сервере, через который проходит запрос. Проверяем каждый вариант отдельно: разные компоненты могут возвращать собственные шаблоны ошибок.
В ответ не должны попадать трассировка стека, абсолютные пути, SQL-запросы, имена таблиц, ключи, токены и значения. Но техническая информация об ошибке не должна исчезать совсем. Пользователю нужен безопасный ответ без деталей реализации, а разработчику — данные, по которым можно найти причину сбоя. Поэтому при наличии доступа QA дополнительно проверяет, что ошибка попала во внутреннюю систему логирования или мониторинга: Sentry или ELK.
Получается два уровня проверки: клиент получает безопасное сообщение без stack trace, SQL и секретов, а во внутренней системе остаётся полный контекст ошибки, необходимый для диагностики.
Проверяем не только тело. Служебная информация может оказаться в заголовках, HTML-странице стандартной ошибки или сообщении, которое вернул внешний сервис.
Пример проверки в Postman:
const responseText = pm.response.text().toLowerCase(); const forbiddenFragments = [ "stack trace", "traceback", "sqlexception", "/var/www/", "password=", "secret=" ]; pm.test("Ответ не раскрывает внутренние данные", () => { forbiddenFragments.forEach((fragment) => { pm.expect(responseText).not.to.include(fragment); }); });
Проверяем аутентификацию и права доступа: 3 проверки
Аутентификация отвечает на вопрос «кто делает запрос», а авторизация — «что этому пользователю разрешено». Валидный токен подтверждает личность, но сам по себе не даёт доступ ко всем заказам и функциям.
14. Запрос без валидной аутентификации
Повторяем GET /orders/8457 без заголовка Authorization, с повреждённым токеном и с токеном, срок действия которого истёк.
Для Bearer-аутентификации RFC 6750 связывает истёкший или некорректный токен со статусом 401 и ошибкой invalid_token.
Проверяем:
API не принимает токен из неожиданного места, если контракт требует
Authorization: Bearer <token>;просроченный, отозванный и изменённый токены не дают доступ;
ответ содержит заданный контрактом заголовок
WWW-Authenticate, который сообщает клиенту поддерживаемую схему аутентификации;неуспешная аутентификация не раскрывает данные заказа.
Пример проверки в Postman:
pm.test("Невалидный токен отклонён", () => { pm.response.to.have.status(401); }); pm.test("Сервер вернул Bearer challenge", () => { pm.expect(pm.response.headers.get("WWW-Authenticate")) .to.include("Bearer"); });
15. Доступ к чужим объектам
Создаём заказ от имени пользователя Б, сохраняем его id, затем авторизуемся как пользователь А и отправляем GET, PATCH и DELETE на тот же адрес. Проверка должна выполняться для каждого метода, запрет чтения не гарантирует запрет изменения или удаления.
OWASP называет эту уязвимость BOLA — Broken Object Level Authorization, или нарушением авторизации на уровне объекта. Она возникает, когда сервер принимает идентификатор, но не проверяет, может ли текущий пользователь работать именно с этим объектом.
Конкретный статус зависит от контракта. API может вернуть 403, если сообщает о недостаточных правах, или 404, если не раскрывает, что чужой объект существует. В обоих случаях пользователь А не должен получить данные заказа Б, изменить или удалить его.
Пример проверки в Postman:
pm.test("Чужой заказ недоступен", () => { // 403 — отказ по правам, 404 — если API скрывает существование заказа pm.expect(pm.response.code).to.be.oneOf([403, 404]); }); pm.test("В ответе нет данных чужого заказа", () => { const contentType = pm.response.headers.get("Content-Type") || ""; if (contentType.includes("application/json")) { const body = pm.response.json(); // тело — это ошибка, а не объект заказа с полями владельца pm.expect(body).to.not.have.property("user_id"); pm.expect(body).to.not.have.property("items"); } });
16. Доступ по ролям и функциям
Теперь проверяем не конкретный заказ, а право вызвать функцию. Обычный пользователь не должен получить доступ к GET /admin/orders, изменить чужую роль или подтвердить оплату административным методом, даже если угадает адрес эндпоинта и сформирует корректный запрос.
OWASP называет этот класс ошибок BFLA — Broken Function Level Authorization, или нарушением авторизации на уровне функции. Проверяем сочетание роли, эндпоинта и HTTP-метода: чтение может быть разрешено, а изменение или удаление — нет.
При валидной аутентификации, но недостаточных правах ожидаем предусмотренный контрактом отказ — обычно 403. Сервер не должен выполнять действие частично или возвращать закрытые поля вместе с сообщением об ошибке.
Здесь важно не путать 401 Unauthorized и 403 Forbidden. 401 означает, что запрос не прошёл аутентификацию: например, токена нет или он недействителен. Если пользователь успешно аутентифицирован, но ему не хватает прав на операцию, обычно ожидаем 403 Forbidden. Исключение — случаи, когда API намеренно скрывает существование ресурса и по контракту возвращает другой статус, например, 404.
Пример проверки в Postman:
pm.test("Обычному пользователю запрещена функция", () => { pm.response.to.have.status(403); }); pm.test("Список заказов не вернулся", () => { const contentType = pm.response.headers.get("Content-Type") || ""; if (contentType.includes("application/json")) { // при 403 тело — ошибка, а не массив заказов pm.expect(pm.response.json()).to.not.be.an("array"); } });
Проверяем состояние системы и бизнес-логику: 4 проверки
Ответ может выглядеть правильным, хотя данные не сохранились, остаток списался дважды или другой эндпоинт возвращает старую версию объекта. Поэтому после проверки статуса, заголовков и тела смотрим, что изменилось в системе.
17. Состояние объекта после операции
После POST сохраняем идентификатор заказа и выполняем GET /orders/{id}. После PATCH повторяем GET и проверяем новые значения. После DELETE убеждаемся, что объект больше недоступен или имеет оговорённый в контракте статус удаления.
В Postman идентификатор можно сохранить в переменную коллекции — именованное значение, доступное всем запросам внутри одной коллекции. В примерах ниже эта переменная называется order_id.
После операции проверяем сам объект и значения полей, которые рассчитывает или обновляет сервер: дату изменения, версию, итоговую сумму и статус.
Не все API работают по схеме «отправили запрос → операция сразу завершилась». Например, создание заказа может выполняться асинхронно: POST /orders возвращает 202 Accepted, после чего задача отправляется в очередь, а сам заказ появляется через некоторое время.
В таком сценарии нельзя сразу отправить один GET, не найти заказ и считать тест проваленным. В автотесте используют retry/polling: повторяют запрос через заданный интервал, пока заказ не перейдёт в ожидаемое состояние или не закончится установленный таймаут. Максимальное время ожидания и допустимые промежуточные состояния берём из контракта или требований.
Если у QA есть доступ к внутренней инфраструктуре, дополнительно можно проверить по логам или мониторингу, что сообщение действительно попало в очередь и начало обрабатываться. Например, для этого могут использовать Grafana и логи брокера RabbitMQ или Kafka.
Пример проверки в Postman:
const order = pm.response.json(); const expectedId = pm.collectionVariables.get("order_id"); pm.test("GET вернул созданный заказ", () => { pm.expect(String(order.id)).to.eql(expectedId); pm.expect(order.status).to.eql("created"); });
18. Связанные бизнес-правила и побочные эффекты
Проверка 5 оценивала вычисления внутри одного ответа. Теперь смотрим шире: после заказа двух единиц товара остаток должен уменьшиться на 2, после отмены — восстановиться, а связанная платёжная или складская операция должна перейти в правильное состояние.
Список побочных эффектов берём из требований. Это могут быть резерв товара, запись в историю, уведомление, начисление бонусов или создание задания для доставки.
Если операция затрагивает расчёты, например, сумму заказа, скидку или возврат, ожидаемое значение снова определяем по бизнес-правилам из требований, а не по упрощённой формуле из ответа API.
Для каждого эффекта фиксируем исходное значение, выполняем операцию и запрашиваем состояние повторно. Так тест не зависит от того, что основной эндпоинт написал в собственном ответе.
Пример проверки в Postman:
const stockBefore = Number( pm.collectionVariables.get("stock_before") ); const quantity = Number( pm.collectionVariables.get("ordered_quantity") ); const stockAfter = pm.response.json().stock; pm.test("Остаток уменьшился на количество в заказе", () => { pm.expect(stockAfter).to.eql(stockBefore - quantity); });
19. Повторный запрос и идемпотентность
Повтор запроса возможен не только из-за пользователя. Клиент может не получить ответ из-за обрыва соединения и отправить операцию снова. Поэтому проверяем, приводит ли одинаковый запрос к ожидаемому состоянию после второго и последующих вызовов.
По RFC 9110 методы PUT и DELETE, а также безопасные методы, которые не должны менять состояние сервера, считаются идемпотентными — эффект нескольких одинаковых запросов должен совпадать с эффектом одного. POST по умолчанию к ним не относится. Защиту от дублей для него проверяем только тогда, когда она предусмотрена контрактом.
В нашем сценарии дважды отправляем DELETE /orders/8457. Второй ответ может отличаться по статусу, например, вернуть 404 вместо 204, но заказ не должен восстановиться, а остаток товара не должен измениться повторно.
Пример проверки в Postman:
pm.test("Повторный DELETE не изменил итоговое состояние", () => { pm.expect(pm.response.code).to.be.oneOf([204, 404]); }); // Управление порядком запросов — вне pm.test. // Сама проверка, что заказ удалён, — в запросе «Проверить удаление». // setNextRequest работает только при запуске коллекции, не при Send. pm.execution.setNextRequest("Проверить удаление");
Сам запрос «Проверить удаление» — это GET /orders/8457. В нём проверяем, что удалённый заказ больше недоступен:
pm.test("Удалённый заказ больше не доступен", () => { pm.response.to.have.status(404); });
20. Согласованность связанных эндпоинтов
Финальная проверка объединяет сценарий в цепочку: POST → GET → PATCH → GET → DELETE → GET. Каждый следующий запрос использует идентификатор из первого ответа и проверяет актуальное состояние, а не заранее записанный пример.
Ищем расхождения между эндпоинтами: создание вернуло один адрес, чтение — другой, изменение прошло успешно, но GET показывает старые данные, удаление подтвердилось, но заказ остался в списке.
Если система использует кэш или выполняет операцию в фоне, допустимая задержка должна быть описана в требованиях. Иначе произвольное ожидание скроет дефект, а слишком короткое заставит тест падать на нормальном переходном состоянии.
Пример проверки в Postman:
const order = pm.response.json(); const expectedId = pm.collectionVariables.get("order_id"); const expectedStatus = pm.collectionVariables.get("expected_order_status"); pm.test("Эндпоинт видит актуальную версию заказа", () => { pm.expect(String(order.id)).to.eql(expectedId); pm.expect(order.status).to.eql(expectedStatus); });
Куда копать дальше: проверки для мидла
Базовые 20 проверок строились вокруг одного сквозного сценария. Мидл расширяет набор с учётом архитектуры API, конкурентных запросов, ограничений инфраструктуры и способов интеграции с другими сервисами.
Вот несколько примеров проверок:
Пагинация, сортировка и фильтрация. Для offset- и cursor-пагинации проверяйте первую и последнюю страницу, максимальный размер страницы и некорректный курсор. Отдельный сценарий — изменение данных между двумя запросами: проверяйте дубли, пропуски и стабильность порядка в соответствии с контрактом.
Ограничение частоты запросов. Проверяйте лимиты по токену, пользователю или IP, если они различаются: запросы до границы, на самой границе и после неё, а также параллельную отправку. После исчерпания лимита API должен вернуть предусмотренный контрактом статус 429 и заголовок Retry-After, а затем вовремя сбросить счётчик.
Версионирование и обратная совместимость. Запускайте старую версию клиента с новой версией API и сравнивайте контракты. Потенциально ломающие изменения — удаление поля, смена его типа, сужение списка допустимых значений, новый обязательный параметр или изменение поведения устаревшего эндпоинта.
Конкурентные обновления и оптимистическая блокировка. Одновременно отправляйте PATCH-запросы к одному объекту и проверяйте потерянные обновления, дубли и конфликт версий. Если API использует ETag и If-Match, запрос с устаревшей версией не должен молча перезаписывать новые данные: сервер отклоняет его заданным контрактом статусом, часто 412.
Таймауты, повторы и деградация зависимостей. Имитируйте медленный или недоступный платёжный, складской или другой внешний сервис. Проверяйте контролируемый ответ, например, 5xx или 504, правила повторной отправки и отсутствие частично выполненной операции. Повтор после таймаута не должен создавать второй заказ или дважды списывать деньги.
Фича-флаги. Новый эндпоинт или отдельная функциональность могут быть доступны только при включённом feature flag. Проверяйте оба состояния: при включённом флаге функция работает штатно, при выключенном — недоступна или работает по старому сценарию. Конкретный ожидаемый ответ определяем по контракту. Это может быть, например, 404 или 403. Отдельно проверяем поведение без явно переданного значения флага, если такой сценарий предусмотрен системой.
Асинхронные операции и согласованность с задержкой. Статус 202 Accepted означает, что запрос принят, но операция ещё может выполняться в фоне. Проверяйте идентификатор задачи или адрес для получения статуса, допустимые переходы, обработку ошибки, предельное время выполнения и защиту от повторного запуска той же операции.
Вебхуки и события. Проверяйте подпись события, задержку доставки, повтор после неуспешного ответа, дубли и нарушение порядка. Если одно событие приходит несколько раз, обработчик не должен повторно списывать деньги, начислять бонусы или менять состояние объекта.
Кэширование и условные запросы. Проверяйте Cache-Control, ETag, If-None-Match и ответ 304 Not Modified. После PATCH или DELETE устаревшая копия не должна продолжать возвращаться, а данные одного пользователя — попадать в кэшированный ответ другого.
Набор выбирают по контракту и архитектуре системы, а ожидаемое поведение заранее согласуют с разработчиком или архитектором.
Как автоматизировать повторяемые проверки в Postman
Ручная проверка нужна, пока QA исследует поведение и уточняет ожидаемый результат. Когда сценарий стал стабильным, запросы и проверки можно собрать в коллекцию Postman и запускать повторно.
Простая схема выглядит так:
в окружение — набор переменных для конкретного стенда — выносим
base_urlиtoken;после
POST /ordersсохраняемorder_idв переменную коллекции;следующие запросы обращаются к
{{base_url}}/orders/{{order_id}};в
Post-responseкаждого запроса оставляем проверки, показанные в разделах выше;Collection Runner— встроенный запуск всей коллекции. Он последовательно отправит запросыPOST → GET → PATCH → GET → DELETE → GETи покажет результат каждого теста.
Сохранить идентификатор и явно передать управление следующему запросу можно так:
const order = pm.response.json(); pm.collectionVariables.set( "order_id", String(order.id) ); pm.execution.setNextRequest("Получить заказ");
По умолчанию Postman выполняет запросы коллекции в заданном порядке. Функция pm.execution.setNextRequest() нужна, если порядок должен зависеть от результата текущего шага. Она работает при запуске коллекции, но не при одиночном нажатии Send.
Коллекцию можно запускать вручную, по расписанию или в CI/CD — процессе, который автоматически собирает, проверяет и доставляет изменения. Для локального запуска через Postman CLI команда выглядит так:
postman collection run "<collection-id>" \ -e "<environment-id>"
Автоматизируйте только устойчивые проверки. Если ожидаемый результат ещё меняется или зависит от неописанной бизнес-логики, скрипт закрепит неопределённость, а не устранит её.
Чек-лист: 20 проверок API
Это компактная версия всех проверок. Её можно использовать при ревью тест-кейсов или как основу для коллекции Postman.
№ | Проверка | Пример | Ожидаемый результат |
1 | Статус-код |
|
|
2 | Структура ответа | Тело успешного ответа | Все обязательные поля, нет неожиданных |
3 | Типы и форматы |
| Типы и форматы соответствуют схеме |
4 | Значения данных | Товар, количество, адрес | Ответ совпадает с запросом и данными системы |
5 | Бизнес-логика ответа |
| Расчёты и начальный статус верны |
6 | Обязательные поля | Нет |
|
7 | Пустые и null-значения |
| Недопустимые значения отклонены |
8 | Тип и формат входа |
|
|
9 | Граничные значения |
| Границы обработаны по контракту |
10 | Неизвестные поля |
| Клиент не меняет серверные свойства |
11 | Статус ошибки | Неверный ID, нет объекта, конфликт | Для каждой причины — ожидаемый статус |
12 | Тело ошибки | Ошибка валидации | Стабильная структура и понятная причина |
13 | Безопасность ошибки | Сбой приложения или БД | Нет стека, SQL, путей и секретов |
14 | Аутентификация | Нет токена, токен испорчен или истёк |
|
15 | Доступ к объекту | Пользователь А запрашивает заказ Б |
|
16 | Доступ к функции | Обычный пользователь вызывает |
|
17 | Состояние объекта |
| Состояние соответствует операции |
18 | Побочные эффекты | Остаток до и после заказа | Связанные сущности изменились правильно |
19 | Повтор и идемпотентность | Повторный | Нет повторного побочного эффекта |
20 | Согласованность эндпоинтов |
| Все методы видят актуальное состояние |
Чек-лист охватывает базовое функциональное тестирование HTTP API: контракт, входные данные, ошибки, доступ и состояние системы. Это отправная точка, а не полный план тестирования API.
Нагрузку, безопасность, совместимость контрактов между сервисами, интеграции и отказоустойчивость проверяют отдельно. Для них нужны свои сценарии, данные, метрики и инструменты.
Для первого рабочего набора достаточно пройти все двадцать проверок вручную на одном сквозном сценарии, зафиксировать ожидаемые результаты и автоматизировать те шаги, которые команда будет повторять после каждого изменения API.
❯ Где потренироваться и чему научиться
Тестирование API растёт только на практике: чем больше задач прошли руками, тем быстрее видите, где всё отвалится. Если хочется двигаться дальше, но пока присматриваетесь, начните с чего-то небольшого и бесплатного:
вводного курса «Инженер по тестированию» — 5 дней демодоступа: попробовать тестирование на реальных задачах и понять, ваше ли это ещё до оплаты;
практической программы «ИТ в действии» — примерить 6 ИТ-ролей на 8 мини-проектах и понять, куда двигаться;
вводного курса «Кибербезопасность» — если security-проверки из статьи зацепили: разобраться с Linux и основами информационной безопасности;
практической программы «ИИ в деле: ускорьте свою работу» — автоматизировать рутину нейросетями на 4 мини-проектах;
вводного курса «Нейросети для каждого» — за 3 занятия начать применять нейросети в работе, даже если раньше их не открывали.
А если хочется не пробовать по чуть-чуть, а закрыть то, что в статье осталось за скобками, — автотесты, нагрузку, безопасность — и вырасти в грейде и доходе, стоит смотреть на системное обучение с наставниками и практикой на реальных проектах:
на курсе «Инженер по ручному тестированию» — войти в тестирование без кода за 4 месяца: тест-дизайн, чек-листы, баг-репорты;
на курсе «Инженер по тестированию» — дорасти с нуля до QA-инженера за 8 месяцев: ручное плюс автотесты на Python или Java, 20+ проектов в портфолио и помощь с трудоустройством;
на расширенном курсе «Инженер по тестированию» — для тех, кто упёрся в потолок: нагрузочное и security-тестирование, анализ трафика, кросс-платформенность и выход на мидл+;
на курсе «Нейросети для разработчиков» — за 7 недель встроить ИИ в работу: генерация кода, автотесты, CI/CD и код-ревью, чтобы писать быстрее и чище;
на курсе «ИИ-разработчик» — перейти из тестирования в ИИ-разработку за 6 месяцев: от работы с API до ИИ-агентов на боевом стеке (OpenAI API, FastAPI), 2 диплома.

