Тестирование с pytest

Первые тесты на 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 в блоке падения и финальная сводка с количеством прошедших и упавших тестов. Это превращает запуск тестов в быстрый цикл: запустили, нашли упавшую проверку, посмотрели фактическое значение, поправили код или ожидание.