Всё нужное уже разобрано: словари, циклы, функции, assert и форма pytest.
Осталось собрать из этого проект, который сам шлёт запросы сервису и проверяет,
что тот ответил.
Тест шлёт запрос и проверяет четыре слоя, а качество самого теста доказывается только на сломанном сервисе.
Из чего состоит проект с тестами
tests/
conftest.py общая подготовка: клиент, вход, данные
test_products.py тесты каталога
test_orders.py тесты заказов
requirements.txt список библиотек
Библиотек нужно ровно две: pytest запускает тесты, requests отправляет
запросы.
pip install pytest requests
В conftest.py держат подготовку, которая нужна каждому тесту: клиента с
адресом сервиса и вход под пользователем. Это фикстуры — как они работают,
разобрано в статье про pytest.
import pytest
import requests
БАЗА = "https://shop.example.com/api"
@pytest.fixture
def client():
return requests.Session()
@pytest.fixture
def заголовки(client):
ответ = client.post(f"{БАЗА}/auth/login",
json={"email": "anna@example.com", "password": "secret123"})
assert ответ.status_code == 200, "не удалось войти — тесты дальше бессмысленны"
return {"Authorization": "Bearer " + ответ.json()["token"]}
Что проверять в ответе
Четыре слоя, от дешёвого к ценному.
Код ответа — 200, 201, 404, 422. Одна строка assert ответ.status_code == 201,
и она есть в каждом тесте.
Значимые поля тела. Вот здесь ошибаются чаще всего: сравнивают тело ответа целиком и привязывают тест к каждому полю, какое сервис вернёт.
живой пример
# ошибка: так не надо — сравнили тело целиком и упали из-за нового поля
тело = {"id": "ord-042", "status": "NEW", "total": 4990, "createdAt": "2026-03-01"}
assert тело == {"id": "ord-042", "status": "NEW", "total": 4990}
Запустить
Запуск примеров доступен в платном доступе. Там этот же код выполняется прямо в статье: редактор, запуск и проверка рядом с абзацем. Три дня бесплатно →
живой пример
# так надо — проверяем поля, ради которых тест написан
тело = {"id": "ord-042", "status": "NEW", "total": 4990, "createdAt": "2026-03-01"}
assert тело["status"] == "NEW"
assert тело["total"] == 4990
print("проверили значимые поля")
Запустить
Запуск примеров доступен в платном доступе. Там этот же код выполняется прямо в статье: редактор, запуск и проверка рядом с абзацем. Три дня бесплатно →
Сервис добавил createdAt и ничего не сломал, а первый тест покраснел на ровном
месте.
Заголовки, на которые полагается клиент. Например Location у созданного
объекта: по нему клиент идёт за результатом.
Последствия в системе. Самое ценное и чаще всего забытое: заказ создан — а остаток товара уменьшился? Ответ 201 об этом не говорит.
def test_остаток_уменьшается(client, заголовки):
было = client.get(f"{БАЗА}/products/p-01").json()["inStock"]
client.post(f"{БАЗА}/orders",
json={"productId": "p-01", "quantity": 2}, headers=заголовки)
стало = client.get(f"{БАЗА}/products/p-01").json()["inStock"]
assert стало == было - 2
Проверять отказы не менее важно
Тесты только на счастливый путь пропускают половину дефектов. На каждый сценарий стоит задать три вопроса: что при неверных данных, что без прав, что при конфликте.
def test_заказ_без_токена(client):
ответ = client.post(f"{БАЗА}/orders", json={"productId": "p-01", "quantity": 1})
assert ответ.status_code == 401
def test_пустое_тело(client, заголовки):
ответ = client.post(f"{БАЗА}/orders", json={}, headers=заголовки)
assert ответ.status_code == 422
поля = [д["field"] for д in ответ.json()["error"]["details"]]
assert "productId" in поля
И отдельно — что отказ ничего не изменил: остаток на месте, заказ не создан. Отказ, который успел что-то поменять, — частая находка при проверке прав.
Данные готовят запросом, а не кликами
Тесту нужен оплаченный заказ — он не оформляет его через интерфейс, а создаёт запросом: быстрее и повторяемо.
@pytest.fixture
def оплаченный_заказ(client, заголовки):
return client.post(f"{БАЗА}/orders",
json={"productId": "p-01", "quantity": 1}, headers=заголовки).json()
И каждый тест готовит данные себе сам. Тесты, которые передают состояние друг другу, ломаются от смены порядка запуска и падают «по очереди» без причины.
Зелёный тест ничего не гарантирует
Тест — такой же код, и в нём тоже бывают ошибки. Тест, который ничего не проверяет, зелёный всегда.
# так не надо — запрос отправлен, а проверки нет
def test_заказ(client, заголовки):
client.post(f"{БАЗА}/orders", json={"productId": "p-01", "quantity": 1}, headers=заголовки)
assert True
# так надо — тест заканчивается проверкой ответа
def test_заказ(client, заголовки):
ответ = client.post(f"{БАЗА}/orders", json={"productId": "p-01", "quantity": 1}, headers=заголовки)
assert ответ.status_code == 201
assert ответ.json()["status"] == "NEW"
Проверить свои тесты можно единственным честным способом: сломать сервис и посмотреть, заметят ли они это. Отключите валидацию, уберите проверку токена, перестаньте списывать остаток — сколько тестов покраснеет? Те, что остались зелёными, не проверяют ничего.
В тренажёре автотестов API это происходит само: тесты прогоняются против десяти сломанных версий сервиса, и в отчёте видно, какие поломки они поймали, а какие прошли мимо. Зачёт ставится, только если тест хоть что-то ловит.
Коротко
- Проекту нужны две библиотеки:
pytestиrequests; общая подготовка живёт вconftest.py. - Проверяют четыре слоя: код ответа, значимые поля, заголовки и последствия в системе.
- Тело ответа не сравнивают целиком: новое поле роняет такой тест без причины.
- Отказы проверяют наравне со счастливым путём — и отдельно то, что отказ ничего не изменил.
- Зелёный тест ничего не гарантирует, пока не показал, что краснеет на сломанном сервисе.
Что почитать дальше
- pytest: первый настоящий тест — если нужно освежить форму.
- Разбор данных — как доставать поля из ответа.
- assert и ошибки — как сделать падение теста понятным.
- API в Postman — те же запросы руками, когда нужно быстро посмотреть.