← назад к разделу

К этому моменту у вас есть всё: переменные и строки, словари и списки, циклы, функции, assert и форма pytest. Осталось собрать из этого то, ради чего всё затевалось, — автотесты API.

тест POST /orders сервисмагазина 201 + тело код ответа поля тела заголовки остаток на складе сервис сломали: валидация выключенахороший тест обязан покраснеть зелёный тест ничего не значит, пока не краснел на поломке

Тест шлёт запрос и проверяет четыре слоя, а качество самого теста доказывается только на сломанном сервисе.

Из чего состоит проект с тестами

tests/
  conftest.py          общие фикстуры: клиент, вход, тестовые данные
  test_products.py     тесты каталога
  test_orders.py       тесты заказов
requirements.txt       список библиотек

Библиотек нужно ровно две: pytest — запускает тесты, requests — отправляет запросы. Ставятся одной командой:

pip install pytest requests

В conftest.py создают клиента — объект, который помнит адрес сервиса и заголовки:

import pytest
import requests

БАЗА = "https://shop.example.com/api"


@pytest.fixture
def client():
    сессия = requests.Session()
    сессия.base_url = БАЗА
    return сессия


@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"]}

В нашем тренажёре фикстура client уже готова и ходит в игрушечный сервис — поэтому там код теста такой же, а настраивать ничего не нужно.

Что проверять в ответе

Четыре слоя, от дешёвого к дорогому.

Код ответа. Самое первое: 200, 201, 404, 422. Проверяется всегда.

assert ответ.status_code == 201

Значимые поля тела. Не всё тело целиком: сравнение с полным словарём ломается от любого нового поля, которое обратную совместимость не нарушает.

заказ = ответ.json()
assert заказ["status"] == "NEW"
assert заказ["total"] == 4990

Заголовки, когда они часть контракта. Например, Location у созданного объекта — по нему клиент идёт за результатом.

Последствия в системе. Самое ценное и чаще всего забытое: заказ создан — а остаток товара уменьшился? Ответ 201 об этом не говорит.

было = 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, заголовки):
    ответ = client.post(f"{БАЗА}/orders",
                        json={"productId": "p-01", "quantity": 1}, headers=заголовки)
    return ответ.json()

И каждый тест готовит данные себе сам. Тесты, которые передают состояние друг другу, ломаются от смены порядка запуска и падают «по очереди» без причины.

Зелёный тест ничего не гарантирует

Главная мысль, ради которой стоило дочитать. Тест — такой же код, и в нём тоже бывают ошибки. Тест, который ничего не проверяет, зелёный всегда:

живой пример

def test_заказ(client, заголовки):
    client.post(f"{БАЗА}/orders", json={"productId": "p-01", "quantity": 1}, headers=заголовки)
    assert True          # ← так бывает чаще, чем хотелось бы
Запустить

Запуск примеров доступен в платном доступе. Там этот же код выполняется прямо в статье: редактор, запуск и проверка рядом с абзацем. Неделя бесплатно →

Проверить свои тесты можно единственным честным способом: сломать систему и посмотреть, заметят ли они это. Отключите валидацию, уберите проверку токена, перестаньте списывать остаток — сколько тестов покраснеет? Те, что остались зелёными, не проверяют ничего.

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

Куда двигаться дальше

Когда тесты на API пишутся уверенно, дальше обычно идут:

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

Но порядок именно такой: сначала уверенные тесты API, потом всё остальное.

Что решать

Банк «Автотесты API» — десять задач ровно по этой статье: коды ответов и поля, постраничная выдача, вход и токен, валидация, конфликты, создание заказа с последствиями, чужие данные и повторный запрос.

Коротко

  • Проекту с тестами нужны две библиотеки: pytest и requests; общие фикстуры живут в conftest.py.
  • Проверяют четыре слоя: код ответа, значимые поля, заголовки контракта и последствия в системе.
  • Отказы проверяют наравне со счастливым путём — и отдельно то, что отказ ничего не изменил.
  • Данные готовят запросами, каждый тест — себе сам; передача состояния между тестами делает их нестабильными.
  • Зелёный тест ничего не гарантирует, пока не показал, что краснеет на сломанной системе.

Что почитать дальше