Всё, что нужно для теста, уже есть: данные, проверки, 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 не говорят ничего. Имя читается как утверждение
о поведении — test_итог_равен_сумме_позиций,
test_отменённый_заказ_не_оплачивается, test_пустая_корзина_не_оформляется.
Тогда по упавшему тесту видно, что сломалось, ещё до чтения кода.
Фикстура: подготовка, которую не копируютспросят на собеседовании
Одну и ту же подготовку не копируют в каждый тест, а выносят в фикстуру —
функцию с пометкой @pytest.fixture:
import pytest
@pytest.fixture
def заказ():
return {"id": "ord-042", "status": "NEW",
"items": [{"qty": 2, "price": 100}]}
def test_итог_равен_сумме_позиций(заказ):
итого = sum(п["qty"] * п["price"] for п in заказ["items"])
assert итого == 200
Тест получает результат фикстуры, просто назвав параметр её именем, — pytest сам
вызовет её перед тестом. Общие фикстуры кладут в conftest.py рядом с тестами:
оттуда они видны всем тестам в папке, импортировать ничего не нужно.
Параметризация: один тест вместо десятиспросят на собеседовании
Когда у тестов отличаются только данные, копировать тест не нужно:
import pytest
@pytest.mark.parametrize("статус, ожидаем", [
("NEW", True),
("SHIPPED", False),
("CANCELLED", False),
])
def test_можно_ли_отменить(статус, ожидаем):
assert (статус == "NEW") is ожидаем
pytest выполнит тест три раза и покажет случаи в отчёте по отдельности — видно, какой именно упал. Добавить ещё один статус — одна строка в списке.
Тест, который ничего не проверяет
Самый опасный тест — тот, что выполняет действия и ни разу не говорит assert:
он зелёный всегда.
живой пример
# так не надо — проверки нет, тест зелёный при любом поведении
def test_заказ_оплачен():
заказ = {"status": "NEW"}
заказ["status"] = "PAID"
test_заказ_оплачен()
print("прошёл, хотя ничего не проверил")
Запустить
Запуск примеров доступен в платном доступе. Там этот же код выполняется прямо в статье: редактор, запуск и проверка рядом с абзацем. Три дня бесплатно →
живой пример
# так надо — тест заканчивается проверкой
def test_заказ_оплачен():
заказ = {"status": "NEW"}
заказ["status"] = "PAID"
assert заказ["status"] == "PAID"
test_заказ_оплачен()
print("прошёл и правда проверил")
Запустить
Запуск примеров доступен в платном доступе. Там этот же код выполняется прямо в статье: редактор, запуск и проверка рядом с абзацем. Три дня бесплатно →
Разница в одной строке, а смысл противоположный: первый тест нельзя сломать, второй поломку заметит.
Что делает тест плохимспросят на собеседовании
Зависимость от порядка. Тест, который работает только после другого, сломается, как только их запустят порознь. Каждый тест готовит себе данные сам.
Проверка всего подряд. Тест на создание заказа не должен заодно проверять формат почты покупателя: упадёт — и непонятно, что сломалось.
Пауза вместо ожидания. time.sleep(5) в тесте — почти всегда признак того,
что ожидание условия заменили надеждой.
Отработать здесь: «Автотесты API» — те же test_-функции и тот же assert, но на работающем сервисе.
Коротко
- pytest находит тесты по приметам: файл
test_*.py, функцияtest_*, внутри обычныйassert. - В тесте видны три части: подготовка, действие, проверка.
- Имя теста — утверждение о поведении; по нему понятно, что сломалось.
- Фикстура (
@pytest.fixture) — подготовка, которую не копируют; общие лежат вconftest.py, а@pytest.mark.parametrizeзаменяет десять похожих тестов одним. - Тест без
assertзелёный всегда; плохой тест ещё зависит от порядка, проверяет всё подряд или спит вместо ожидания.
Что почитать дальше
- Автотесты API на Python — собрать всё вместе на настоящем сервисе.
- assert и ошибки — как сделать падение теста понятным.
- Функции — что такое
def, параметр иreturn. - Разбор данных — что именно проверять в ответах.