Pytest: быстрое руководство по тестированию Python

Изображение: пример редактора кода с тестами и запуском тестов в терминале
Тестирование — неотъемлемая часть разработки. Оно обнаруживает ошибки на ранних стадиях, повышает надёжность и упрощает рефакторинг. Pytest позволяет писать компактные, читаемые тесты, которые масштабируются вместе с проектом.
Что вы получите из этого руководства
- Пошаговая установка и запуск pytest
- Примеры тестов: простые, параметризованные, для исключений
- Лучшие практики организации тестов и настройки конфигурации
- Чеклист обязанностей для ролей: разработчик, тестировщик, DevOps
- Отладка неудачных тестов и сценарии отказа
- Шаблоны, критерии приёмки и короткая шпаргалка команд
Установка и виртуальное окружение
Важно: используйте виртуальное окружение, чтобы зависимости тестов не конфликтовали с глобальными пакетами.
Создайте виртуальное окружение в каталоге проекта (например, tests):
python -m venv testsАктивируйте окружение на macOS / Linux:
source tests/bin/activateНа Windows (PowerShell/CMD):
tests\Scripts\activateУстановите pytest через pip:
pip install pytestПроверьте версию pytest:
pytest --versionЕсли команда вернула номер версии — установка прошла успешно.
Примечание: при использовании poetry, pipenv или других инструментов управления зависимостями следуйте их рекомендациям для создания изолированных окружений.
Первый тест: функция сложения
Рассмотрим простую функцию, складывающую два числа:
def add_numbers(a, b):
return a + bКритичные случаи: нечисловые входы (None, строки), большой диапазон значений, плавающие точки.
Создайте файл теста, например test_math.py, и напишите базовый тест:
def test_add_numbers():
assert add_numbers(2, 3) == 5
assert add_numbers(-1, 1) == 0
assert add_numbers(0, 0) == 0Объяснение: assert сравнивает реальный результат с ожидаемым. Pytest автоматически обнаружит функции и файлы, начинающиеся с test_.
Запуск тестов
Перейдите в папку проекта и выполните:
pytestPytest выполнит все тесты и выведет результат. Если тесты не прошли, вы увидите трассировку и конкретные значения, которые привели к провалу.
Пример падения теста — неправильный ожидаемый результат:
def test_add_numbers():
assert add_numbers(2, 3) == 6Pytest покажет входные данные и фактическое значение, что облегчает диагностику.
Изображение: окно терминала с подробным выводом ошибки теста
Проверка исключений с pytest.raises
Чтобы убедиться, что функция корректно реагирует на некорректные входы, используйте pytest.raises:
import pytest
def test_add_numbers_with_invalid_inputs():
with pytest.raises(TypeError):
add_numbers(None, 2)Контекстный менеджер pytest.raises возвращает объект ExceptionInfo, позволяющий проверить сообщение исключения:
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'"Важно: проверка точного текста исключения полезна при специфических ошибках, но делает тесты хрупкими при изменении формулировки исключения. Лучше проверять тип исключения и ключевые слова сообщения.
Параметризованные тесты
Pytest.mark.parametrize позволяет запускать одну тест-функцию с набором входных данных. Это делает тесты компактнее и понятнее:
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) == expectedПреимущества: сразу видно набор случаев, каждый случай считается отдельным тестом в выводе pytest.
Организация тестов и классы
Для группировки тестов используйте модули и каталоги. Можно объединять тесты в классах — pytest обнаруживает классы, начинающиеся с Test:
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 "NoneType" in exc_info.value.args[0]Совет: тестовые классы не должны хранить состояние между тестами. Для общего состояния используйте фикстуры.
Fixtures: настройка и очистка
Фикstуры (fixtures) — способ подготовить состояние перед тестом и выполнить очистку после него. Коротко: фикстура — это функция, помеченная @pytest.fixture, которую вы передаёте в тест как аргумент.
Пример фикстуры:
import pytest
@pytest.fixture
def sample_data():
data = {"a": 1, "b": 2}
yield data
# здесь можно очистить ресурсыИспользование:
def test_using_fixture(sample_data):
assert sample_data["a"] + sample_data["b"] == 3Фикстуры можно параметризовать, композировать и задавать область видимости (function, module, session).
Конфигурация pytest
Для настройки поведения pytest создайте файл pytest.ini или pyproject.toml с секцией [pytest]. Пример pytest.ini:
[pytest]
minversion = 6.0
addopts = -ra -q
testpaths = tests
python_files = test_*.py *_test.pyОпции addopts позволяют по умолчанию добавлять ключи командной строки (например, -q для краткого вывода).
Интеграция в CI
Часто pytest запускают в CI при каждом коммите. Пример простого конвейера (GitHub Actions):
name: CI
on: [push, pull_request]
jobs:
test:
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 pip install --upgrade pip
pip install -r requirements.txt
- name: Run tests
run: |
pytestВажно: включайте покрытие (coverage) и артефакты тестов при необходимости, сохраняйте логи для отладки.
Отладка провалившихся тестов
Шаги для анализа:
- Прочитать сообщение об ошибке и трассировку. Pytest показывает входные данные и фактическое значение.
- Запустить только проблемный тест: pytest path::test_name -k pattern -q
- Добавить -s, чтобы видеть вывод print/логов: pytest -k test_name -s
- Использовать pdb для интерактивной отладки: pytest –pdb
- Проверить окружение: версии библиотек, переменные окружения, фикстуры.
Совет: пишите тесты маленькими и изолированными — это упрощает локализацию ошибки.
Когда тесты дают ложные срабатывания
Примеры причин:
- Нестабильные внешние зависимости (сеть, базы данных) — используйте мокинг
- Состояние, сохраняемое между тестами — используйте фикстуры с правильной областью видимости
- Зависимость от времени — мокируйте время (freezegun или pytest-monkeypatch)
Альтернативы и когда pytest не подходит
Когда рассмотреть альтернативы:
- unittest — встроенный модуль, лучше для простых проектов или при необходимости совместимости со старыми плагинами
- nose2 — похож на pytest, но менее распространён
Pytest — выбор по умолчанию для большинства новых проектов благодаря удобству и сообществу.
Практическое руководство: чеклист для ролей
Чеклист разработчика:
- Добавил/обновил тесты для нового кода
- Тесты запускаются локально в виртуальном окружении
- Не оставил print() и дебаг-код
- Тесты покрывают пограничные случаи
Чеклист тестировщика:
- Запустил весь набор тестов
- Проверил параллельный запуск (pytest-xdist) при необходимости
- Подготовил тестовые данные и фикстуры
Чеклист DevOps:
- Интеграция в CI настроена и проходит
- Артефакты тестового покрытия публикуются
- Логи тестов сохраняются для расследования инцидентов
Критерии приёмки
- Все новые и изменённые функции имеют тесты
- Тесты проходят локально и в CI
- Покрытие критичных модулей соответствует внутренним требованиям команды
- Нет неустранимых предупреждений или временных зависимостей
Мини-методология для написания тестов
- Пишите тест до фиксации бага (TDD) либо сразу после реализации.
- Делайте тесты малыми и независимыми.
- Избегайте внешних влияний — мокируйте запросы и файловую систему.
- Используйте parametrization для наборов данных.
- Покрывайте как позитивные, так и негативные сценарии.
Примеры тест-кейсов для add_numbers
- Позитивные: (2,3)=5, (-1,1)=0, (0,0)=0
- Негативные: (None,2) -> TypeError
- Граничные: большие целые, очень маленькие/большие float
Шпаргалка команд
- Запуск всех тестов: pytest
- Запуск конкретного файла: pytest tests/test_math.py
- Запуск конкретного теста: pytest tests/test_math.py::test_add_numbers
- Запуск с подробным выводом: pytest -v
- Включить pdb при падении: pytest –pdb
- Параллельный запуск: pytest -n auto (плагин pytest-xdist)
Безопасность и конфиденциальность
Не храните реальные секреты в тестовых данных или репозитории. Для интеграционных тестов используйте защищённые переменные окружения в CI и мокируйте внешние сервисы.
Краткая галерея пограничных случаев
- Некорректные типы аргументов
- Плавающие точки: .1 + .2
- Переполнение при очень больших целых (редко в Python)
- Настройки локали/кодировки при работе со строками
1‑строчный глоссарий
- Fixture — подготовка/очистка тестовых данных
- Parametrize — запуск теста с набором входов
- ExceptionInfo — объект с информацией об исключении
- CI — непрерывная интеграция
Короткое резюме
Pytest даёт удобный, масштабируемый и расширяемый набор инструментов для тестирования Python-кода. Начните с простых assert-тестов, используйте parametrization и fixtures для повторного использования и стабильности, интегрируйте тесты в CI и следуйте чеклистам для ролей в команде.
Важное: тесты — инвестиция. Чем раньше вы их добавите, тем меньше времени уйдёт на отладку в будущем.
Похожие материалы
Как устроить идеальную вечеринку для просмотра ТВ
Как распаковать несколько RAR‑файлов сразу
Приватный просмотр в Linux: как и зачем
Windows 11 не видит iPod — способы исправить
PS5: как настроить игровые пресеты