Всё, что нужно для теста, у вас уже есть: данные, проверки, assert. Осталось
оформить это так, как принято в проектах, — чтобы тесты запускались одной
командой, а отчёт показывал, что упало. Этим занимается pytest.
Прогон показывает каждый тест отдельно, а по упавшему сразу видно, какое поведение сломалось.
Как выглядит тест
живой пример
def test_сумма_заказа_считается_по_позициям():
позиции = [{"qty": 2, "price": 100}, {"qty": 1, "price": 50}]
итого = sum(п["qty"] * п["price"] for п in позиции)
assert итого == 250
test_сумма_заказа_считается_по_позициям() # pytest вызовет её сам, здесь — вручную
print("тест прошёл")
Запустить
Запуск примеров доступен в платном доступе. Там этот же код выполняется прямо в статье: редактор, запуск и проверка рядом с абзацем. Неделя бесплатно →
Никакой обёртки: обычная функция с именем, начинающимся на test_, внутри —
обычный assert. Файл называется test_orders.py — тоже с приставкой test_.
По этим двум приметам pytest и находит тесты.
Запуск — одной командой из корня проекта:
pytest
pytest test_orders.py # один файл
pytest -k сумма # только тесты, в имени которых есть «сумма»
pytest -v # с именами каждого теста
Отчёт показывает точку за пройденный тест, букву F за упавший, а для упавших —
строку с assert, значения и сообщение.
Три части теста
У хорошего теста видно три части, и их обычно разделяют пустой строкой:
живой пример
def test_отменённый_заказ_не_оплачивается():
заказ = {"id": "ord-042", "status": "CANCELLED"} # подготовка
можно = заказ["status"] in ("NEW", "PENDING_PAYMENT") # действие
assert можно is False # проверка
test_отменённый_заказ_не_оплачивается()
print("тест прошёл")
Запустить
Запуск примеров доступен в платном доступе. Там этот же код выполняется прямо в статье: редактор, запуск и проверка рядом с абзацем. Неделя бесплатно →
Подготовка — что имеем; действие — что делаем; проверка — чего ждём. Тест, где эти части перемешаны, читается тяжело и чинится ещё тяжелее.
Имя теста — это предложение
test_1, test_orders, test_ok не говорят ничего. Имя должно читаться как
утверждение о поведении:
живой пример
def test_заказ_без_токена_не_создаётся(): ...
def test_итог_равен_сумме_позиций(): ...
def test_отмена_возвращает_деньги_в_течение_суток(): ...
Запустить
Запуск примеров доступен в платном доступе. Там этот же код выполняется прямо в статье: редактор, запуск и проверка рядом с абзацем. Неделя бесплатно →
Когда такой тест падает, отчёт сам объясняет проблему — до чтения кода.
Фикстура: подготовка, которую не копируют
Помните функцию войти из статьи про функции? В pytest
такие вещи оформляют фикстурой:
import pytest
@pytest.fixture
def заголовки(client):
ответ = client.post("/auth/login", json={"email": "anna@example.com", "password": "secret123"})
return {"Authorization": "Bearer " + ответ.json()["token"]}
def test_создание_заказа(client, заголовки):
ответ = client.post("/orders", json={"productId": "p-01", "quantity": 1}, headers=заголовки)
assert ответ.status_code == 201
Механика простая: @pytest.fixture помечает функцию как подготовку, а тест
получает её результат, просто назвав параметр тем же именем. pytest сам вызовет
фикстуру перед тестом.
Общие фикстуры кладут в файл conftest.py рядом с тестами — оттуда они видны
всем тестам в папке, импортировать ничего не нужно.
Параметризация: один тест вместо десяти
Когда отличаются только данные, копировать тест не нужно:
@pytest.mark.parametrize("статус, ожидаем", [
("NEW", True),
("PENDING_PAYMENT", True),
("SHIPPED", False),
("CANCELLED", False),
])
def test_можно_ли_отменить(статус, ожидаем):
assert можно_отменить({"status": статус}) is ожидаем
pytest выполнит тест четыре раза и покажет их в отчёте по отдельности — видно, какой именно случай упал. Добавить пятый статус — одна строка в списке.
Что делает тест плохим
Зависимость от порядка. Тест, который работает только после другого, сломается, как только их запустят порознь или в другом порядке. Каждый тест готовит себе данные сам.
Проверка всего подряд. Тест на создание заказа не должен заодно проверять формат почты покупателя: упадёт — и непонятно, что сломалось.
Отсутствие проверки. Тест, который выполняет действия и ни разу не говорит
assert, зелёный всегда. Это самый опасный вид — он создаёт ощущение покрытия.
Пауза вместо ожидания. time.sleep(5) в тесте — почти всегда признак того,
что ожидание условия заменили надеждой.
Что дальше
В нашем тренажёре автотестов API
всё это уже работает: вы пишете def test_…(client) с обычным assert, тест
запускается по кнопке, а рядом крутится игрушечный сервис магазина. Плюс к
зачёту ваши тесты прогоняют против сломанных версий сервиса — и зачёт
ставится, только если тест эту поломку заметил.
Коротко
- pytest находит тесты по приметам: файл
test_*.py, функцияtest_*, внутри обычныйassert. - В тесте видны три части: подготовка, действие, проверка.
- Имя теста — утверждение о поведении; по нему должно быть понятно, что сломалось.
- Фикстура (
@pytest.fixture) — подготовка, которую не копируют; общие лежат вconftest.py. @pytest.mark.parametrizeзаменяет десять похожих тестов одним с таблицей данных.- Плохой тест: зависит от порядка, проверяет всё подряд, не проверяет ничего или спит вместо ожидания.
Что почитать дальше
- Автотесты API на Python — собрать всё вместе на настоящем сервисе.
- Разбор данных — что именно проверять в ответах.