Тестирование с Pytest: руководство по настройке и лучшим практикам

Зачем нужен Pytest
Тестирование помогает ловить ошибки на ранних стадиях разработки и снижает риски при изменениях кода. Pytest популярен из‑за простого синтаксиса, богатой экосистемы плагинов и гибкой системы фикстур.
Краткое определение: Pytest — фреймворк для тестирования Python, ориентированный на читабельность тестов и их расширяемость.
Important: этот материал практичен — примеры можно копировать и запускать локально.
Установка и изоляция окружения
Перед установкой рекомендуем создать виртуальное окружение, чтобы зависимости тестов не смешивались с глобальными пакетами.
Создайте виртуальное окружение командой:
python -m venv testsАктивируйте окружение на Linux/macOS:
source tests/bin/activateНа Windows используйте команду (точно с двумя обратными слэшами в пути):
tests\\Scripts\\activateУстановите Pytest через pip:
pip install pytestПроверьте установку:
pytest --versionЕсли команда вернула версию, Pytest установлен и готов к использованию.
Первый тест: простая функция сложения
Рассмотрим простую функцию, которая складывает два числа:
def add_numbers(a, b):
return a + bЧто может пойти не так: входные данные могут быть не числовыми (None, строка и т. п.), что приведет к исключению TypeError.
Напишите первый тест, чтобы проверить ожидаемое поведение:
def test_add_numbers():
assert add_numbers(2, 3) == 5
assert add_numbers(-1, 1) == 0
assert add_numbers(0, 0) == 0Этот тест проверяет базовые сценарии. Каждый assert сравнивает фактический результат с ожидаемым.
Как запускать тесты
Перейдите в папку с тестами и выполните:
pytestPytest автоматически найдет файлы, начинающиеся с test_ или заканчивающиеся на _test.py, и запустит тесты.
Если один из assert не совпал с ожидаемым значением, Pytest выведет удобный отчет с входными данными и фактическим результатом.
Изображение показывает типичный вывод: какой assert не прошёл и какие были входные значения.
Проверка исключений с помощью pytest.raises
Чтобы тестировать сценарии, в которых функция должна поднять исключение, используйте контекстный менеджер pytest.raises.
import pytest
def test_add_numbers_with_invalid_inputs():
with pytest.raises(TypeError):
add_numbers(None, 2)Если исключение не будет поднято, тест провалится. Чтобы проверить текст исключения, сохраните контекст в переменную и сравните сообщение:
def test_add_numbers_with_invalid_inputs():
with pytest.raises(TypeError) as exc_info:
add_numbers(None, 2)
assert exc_info.value.args[0] == 'unsupported operand type(s) for +: \'NoneType\' and \'int\''Обратите внимание: строки сравниваются буквально, поэтому тест чувствителен к формулировке исключения.
Параметризованные тесты
Параметризация позволяет запускать одну функцию теста с набором входных данных. Это делает тесты короче и чище:
import pytest
@pytest.mark.parametrize('a,b,expected', [
(2, 3, 5),
(-1, 1, 0),
(0, 0, 0)
])
def test_add_numbers(a, b, expected):
assert add_numbers(a, b) == expectedPytest выполнит три итерации этого теста с разными наборами значений.
Группировка тестов в классах
Для организации большого количества тестов удобно использовать тестовые классы. Класс должен начинаться с Test, чтобы Pytest распознал его:
class TestAddFunction:
@pytest.mark.parametrize('a, b, expected', [
(2, 3, 5),
(-1, 1, 0),
(0, 0, 0),
])
def test_addition_with_numbers(self, a, b, expected):
assert add_numbers(a, b) == expected
def test_add_numbers_with_invalid_inputs(self):
with pytest.raises(TypeError) as exc_info:
add_numbers(None, 2)
assert exc_info.value.args[0] == 'unsupported operand type(s) for +: \'NoneType\' and \'int\''Заметьте: методы класса получают параметр self, но Pytest не использует unittest-инфраструктуру, поэтому можно писать простые тестовые классы без boilerplate.
Фикстуры: подготовка и очистка данных
Фикстуры помогают подготовить окружение для тестов (например, создать временные файлы, подключиться к тестовой БД, настроить мок). Простая фикстура:
import pytest
@pytest.fixture
def sample_data():
return [1, 2, 3]
def test_using_fixture(sample_data):
assert sum(sample_data) == 6Фикстуры можно задавать с различной областью видимости: function, module, class, session — это управляет временем жизни фикстуры.
Полезные приёмы и шпаргалка
Шпаргалка команд:
pytest # запуск всех тестов
pytest -q # короткий вывод
pytest tests/test_mod.py::test_name # запуск одного теста
pytest -k 'expression' # запуск тестов по выражению в имени
pytest -m slow # запуск помеченных тестов
pytest --maxfail=1 # остановиться после первой ошибкиМетки и контроль: используйте @pytest.mark.skip и @pytest.mark.xfail для временного пропуска или пометки известных проблем.
Критерии приёмки для add_numbers
- Функция должна возвращать сумму числовых аргументов.
- При передаче None или несовместимых типов должен быть поднят TypeError.
- При передаче целых и плавающих чисел поведение должно соответствовать математическому сложению.
Критерии приёмки можно формализовать в виде набора тестов, которые выполняются в CI при каждом пулл-реквесте.
Примеры тест-кейсов (edge cases)
- Обычные целые числа: (2, 3) → 5
- Отрицательные и положительные: (-1, 1) → 0
- Ноль и ноль: (0, 0) → 0
- Плавающие точки: (1.5, 2.5) → 4.0
- Типы несовместимы: (None, 2) → TypeError
- Строки: (“2”, 3) → чаще всего TypeError или конкатенация — уточните требуемое поведение
Когда Pytest может не подойти
- Очень лёгкие сценарии, где хватает assert-ов в ad-hoc скриптах — Pytest может быть избыточен.
- Очень специфичные среды с ограничениями на запуск внешних процессов — требуется адаптация фикстур.
- Если в проекте уже используется другой фреймворк с большим количеством интеграционных тестов и миграция невозможна.
Альтернатива: unittest (встроенный модуль) или nose2 — но Pytest обычно проще и гибче.
Интеграция в CI (пример для GitHub Actions)
Файл workflow (.github/workflows/python-tests.yml):
name: CI
on: [push, pull_request]
jobs:
tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: '3.10'
- name: Install dependencies
run: |
python -m venv .venv
source .venv/bin/activate
pip install -U pip
pip install pytest
- name: Run tests
run: pytest -qЭтот пример показывает базовую установку окружения и запуск тестов. Для Windows runner команды активации нужно поменять.
Best practices и паттерны
- Держите тесты небольшими и атомарными — один утверждение на поведение.
- Используйте параметризацию для наборов входных данных.
- Фикстуры применяйте для подготовки окружающей среды и повторно используемых ресурсов.
- Избегайте логики в тестах — тест должен проверять поведение, а не выполнять вычисления, которые могут повторять баг.
- Пишите названия тестов так, чтобы по имени было понятно, что проверяется.
Роль‑ориентированные чек-листы
Для разработчика:
- Созданы тесты для новых функций
- Добавлены тесты на граничные случаи
- Тесты проходят локально и в CI
Для ревьювера:
- Тест покрывает бизнес‑логику, а не реализацию
- Нет избыточных зависимостей в тестах
- Сообщения об ошибках информативны
Маленькая методология: как писать тесты для новой функции
- Определите ожидаемое поведение и граничные случаи.
- Напишите тесты, которые сначала падают (red).
- Реализуйте минимальное исправление (green).
- Рефакторьте код и тесты (refactor).
Эта простая «Red‑Green‑Refactor» практика ускоряет разработку и поддерживает покрытие тестами.
Шпаргалка по плагинам и расширениям
- pytest-cov — отчёты покрытия кода
- pytest-mock — удобная обёртка для mock
- pytest-xdist — параллельный запуск тестов
Используйте плагины, когда проект требует дополнительной функциональности.
Краткий глоссарий
- Фикстура — функция подготовки окружения для теста.
- Параметризация — запуск одного теста с разными входными данными.
- Mark — метка для теста, например slow или integration.
- xfail — ожидаемый провал теста.
Заключение
Pytest — мощный и гибкий инструмент для тестирования Python‑кода. Начав с простых assert и параметризации, вы быстро сможете перейти к фикстурам, запуску тестов в CI и использованию плагинов. Следуйте простым практикам: маленькие тесты, явные критерии приёмки, повторно используемые фикстуры.
Notes: начните с простого набора тестов для ключевых функций, затем расширяйте покрытие по мере роста кода.
Сводка и рекомендации
- Начните с виртуального окружения и установки pytest.
- Пишите как можно более простые и понятные тесты.
- Используйте pytest.raises для проверки исключений.
- Параметризуйте тесты для набора входных данных.
- Включите прогон тестов в CI и добавьте базовое покрытие.
Дополнительные ресурсы: официальная документация Pytest и плагины для покрытия кода и упрощения мокирования.
Похожие материалы
RDP: полный гид по настройке и безопасности
Android как клавиатура и трекпад для Windows
Советы и приёмы для работы с PDF
Calibration в Lightroom Classic: как и когда использовать
Отключить Siri Suggestions на iPhone