Первые тесты на pytest: нормальные сценарии, граничные случаи и исключения
Содержание курса
Запуск pytest и чтение результата PASSED/FAILED
После того как тесты лежат в проекте, их нужно запускать не по одному вручную, а через pytest. Обычно команду выполняют из корня проекта — там, где видны и код приложения, и файлы тестов.
pytest
Если хочется видеть каждый тест отдельной строкой, удобнее включить подробный режим:
pytest -v
В обычной работе -v особенно полезен на первых шагах: сразу видно имя файла, имя тестовой функции и статус рядом с ней.
Для примера возьмём уже знакомые тесты скидки. Представим, что два теста написаны правильно, а в тесте полной скидки при редактировании случайно ошиблись в ожидании: написали == 1 вместо правильного == 0. Тогда короткий вывод в подробном режиме может выглядеть так:
test_prices.py::test_apply_discount_regular_percent PASSED [ 33%]
test_prices.py::test_apply_discount_zero_percent_keeps_price PASSED [ 66%]
test_prices.py::test_apply_discount_full_percent_makes_price_zero FAILED [100%]
PASSED означает, что тестовая функция завершилась без исключений. FAILED означает, что внутри теста произошло исключение: чаще всего это AssertionError, но может быть и любая другая ошибка.
Самая полезная часть при падении находится ниже, в блоке с подробностями. Там pytest показывает, какой тест упал и на какой строке проверка не сошлась.
================================ FAILURES =================================
____________ test_apply_discount_full_percent_makes_price_zero ____________
def test_apply_discount_full_percent_makes_price_zero() -> None:
> assert apply_discount(2000, 100) == 1
E assert 0 == 1
E + where 0 = apply_discount(2000, 100)
test_prices.py:11: AssertionError
Читать такой фрагмент лучше сверху вниз.
Имя после FAILURES говорит, какая тестовая функция упала.
Строка с символом > показывает конкретный assert, на котором выполнение остановилось.
Строки, начинающиеся с E, показывают расшифровку ошибки: что получилось фактически и с чем это сравнивали.
В этом примере функция вернула 0, а тест ожидал 1. Значит, сначала нужно решить не «pytest сломался», а что неверно: код функции или ожидание в тесте. Для случая полной скидки логичнее ожидать ноль, поэтому здесь ошибка именно в тестовом ожидании.
В конце pytest печатает итоговую сводку:
========================= short test summary info ========================= FAILED test_prices.py::test_apply_discount_full_percent_makes_price_zero ======================= 1 failed, 2 passed in 0.04s =======================
Эта строка нужна для быстрой ориентации. Если видите 3 passed, набор зелёный. Если видите 1 failed, 2 passed, не нужно прокручивать весь вывод наугад: в short test summary уже указано точное имя упавшего теста.
Для диагностики обычно хватает трёх мест в отчёте: статус PASSED или FAILED рядом с тестом, строка с assert в блоке падения и финальная сводка с количеством прошедших и упавших тестов. Это превращает запуск тестов в быстрый цикл: запустили, нашли упавшую проверку, посмотрели фактическое значение, поправили код или ожидание.
