Основы pytest: первые тесты в Python

pytest это самый распространённый фреймворк для тестирования в Python. У него простой стартовый интерфейс: вы пишете обычные Python-функции с assert, запускаете pytest в терминале — он сам находит тесты и выводит, что прошло и что упало.

Установка

pip install pytest

Первый тест

Производственный код кладём в example.py, тесты в файл рядом с префиксом test_. По этому префиксу pytest и находит тесты автоматически:

test_example.py
1from example import add2 3def test_add_positive():4    assert add(1, 2) == 35 6def test_add_negative():7    assert add(-1, -2) == -38 9def test_add_mixed():10    assert add(5, -2) == 311 

Тест это обычная функция, имя начинается с test_, внутри используется стандартный Python-овский assert. Никаких специальных классов, наследований, регистрации.

Иллюстрация: папка project с файлами app.py, test_math.py, test_strings.py, helpers.py. pytest сканирует папку и находит файлы с префиксом test_, остальные пропускает

Запуск

В терминале, в директории с тестами:

pytest

pytest пройдёт по всем test_*.py файлам, запустит все test_* функции внутри и покажет результат:

============================= test session starts ==============================
collected 3 items

test_example.py ...                                                      [100%]

============================== 3 passed in 0.02s ===============================

Точка . рядом с именем файла означает «один тест прошёл». Три точки = три прошли. Для подробного вывода с именами каждого теста — флаг -v:

pytest -v
test_example.py::test_add_positive PASSED                                [ 33%]
test_example.py::test_add_negative PASSED                                [ 66%]
test_example.py::test_add_mixed PASSED                                   [100%]

Когда тест падает

Сломаем тест нарочно (assert add(5, -2) == 10 — заведомо неверно) и запустим pytest:

test_example.py::test_add_mixed FAILED                                   [100%]

=================================== FAILURES ===================================
______________________________ test_add_mixed __________________________________

    def test_add_mixed():
>       assert add(5, -2) == 10
E       assert 3 == 10
E        +  where 3 = add(5, -2)

test_example.py:9: AssertionError

pytest показывает:

  • где упало (test_example.py:9: AssertionError)
  • какое выражение не сработало (assert add(5, -2) == 10)
  • что получилось вместо ожидаемого (3 == 10, и что 3 = add(5, -2))

Такой развёрнутый вывод обычно сразу подсказывает причину. Это и есть главный аргумент в пользу обычного assert вместо специальных методов: pytest сам разбирает выражение и показывает интересные части.

Структура теста: Arrange / Act / Assert

По мере роста тестов появляется паттерн, который сильно упрощает их чтение: AAA (Arrange / Act / Assert).

  1. Arrange: готовим данные, создаём объекты, настраиваем состояние.
  2. Act: выполняем то самое действие, которое тестируем.
  3. Assert: проверяем результат.
Python 3.13
def test_user_can_change_email():
    # Arrange
    user = User("Anna", "old@example.com")

    # Act
    user.change_email("new@example.com")

    # Assert
    assert user.email == "new@example.com"

Простой тест assert add(1, 2) == 3 укладывается в одну строку, и AAA там не нужен. Но как только тест больше 3-4 строк, разделение на три блока (комментариями или просто пустыми строками) делает его читаемым с первого взгляда. Это стандарт де-факто в production-коде.

Имена тестов: что_когда_ожидается

В реальных проектах имя теста читается как короткое утверждение о коде. Удобный шаблон: test_<что>_<при каких условиях>_<ожидаемое поведение>:

  • test_add_returns_sum_for_positive_numbers, понятнее, чем test_add_1
  • test_withdraw_fails_when_balance_is_zero, сразу видно, что и почему
  • test_user_email_is_lowercased_after_save, конкретное поведение

Когда такой тест падает в CI, по имени сразу понятно, что именно сломалось, без чтения тела. Это экономит часы дебага в больших проектах.

Проверка понимания

Какое утверждение об основах работы с pytest верно?


В следующей статье возьмём две главные «суперсилы» pytest: фикстуры (для общей настройки тестов) и параметризацию (для запуска одного теста с разными данными).