Основы pytest: первые тесты в Python
После каждой правки хочется убедиться, что ничего не сломалось: запускаете пример, смотрите на вывод, проверяете ещё один. На пятый раз это надоедает, и рутину забирает pytest, самый распространённый фреймворк для тестирования в Python. Тест у него — обычная функция с assert: запускаете pytest в терминале, а он сам находит тесты и показывает, что прошло, а что упало.
Установка
pip install pytest
Первый тест
assert — это оператор самого Python, а не часть pytest. Запись assert выражение ничего не делает, если выражение истинно, и выбрасывает AssertionError, если оно ложно. На этом pytest и стоит: тест — обычная функция, которая либо молча отработала до конца, либо упала на первом же несошедшемся assert.
Производственный код кладём в example.py, тесты в файл рядом с префиксом test_. По этому префиксу pytest и находит тесты автоматически:
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. Никаких специальных классов, наследований, регистрации.

Запуск
В терминале, в директории с тестами:
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 сам разбирает выражение и показывает интересные части.
Проверка, что код падает
Тест фиксирует не только правильный ответ, но и правильную ошибку. Вызов add("a", 1) должен падать с TypeError — это тоже поведение, и его стоит закрепить. Обычным assert такое не проверить: исключение прервёт тест раньше, чем дело дойдёт до проверки. Для этого есть pytest.raises:
Python 3.13import pytest from example import add def test_add_string_and_number_raises(): with pytest.raises(TypeError): add("a", 1)
Тест пройдёт, если внутри блока with вылетит TypeError, и упадёт, если исключения не будет.
Структура теста: Arrange / Act / Assert
По мере роста тестов появляется паттерн, который сильно упрощает их чтение: AAA (Arrange / Act / Assert).
- Arrange: готовим данные, создаём объекты, настраиваем состояние.
- Act: выполняем то самое действие, которое тестируем.
- Assert: проверяем результат.
Python 3.13def 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: фикстуры (для общей настройки тестов) и параметризацию (для запуска одного теста с разными данными).
