Pytest в Python-разработке: простое создание и запуск тестов кода
Для кого эта статья:
- Python-разработчики, желающие улучшить качество своих проектов через тестирование.
- Начинающие тестировщики ПО, заинтересованные в освоении инструментов для тестирования на Python.
Команды разработчиков и QA-специалистов, стремящиеся интегрировать автоматизированное тестирование в CI/CD процессы.
Когда у вас код разрастается до нескольких сотен или тысяч строк, ошибки становятся неизбежными. Один пропущенный тест — и вместо гениального продукта получаете репутационную катастрофу. Pytest спасает положение, позволяя легко создавать, запускать и поддерживать тесты любой сложности. В отличие от встроенного unittest, он проще в освоении и мощнее в применении — идеально для Python-разработчиков всех уровней. Давайте разберемся, как внедрить pytest в рабочий процесс и превратить тестирование из головной боли в удовольствие. 🚀
Хотите стать востребованным IT-специалистом и научиться писать качественные тесты на Python? Курс тестировщика ПО от Skypro — идеальный старт карьеры в QA. Вы не только освоите pytest и другие фреймворки тестирования, но и получите практические навыки работы с реальными проектами под руководством опытных менторов. 94% выпускников трудоустраиваются в течение 3 месяцев после завершения программы. Инвестируйте в свое будущее прямо сейчас!
Pytest для Python: основы установки и настройки среды
Установка pytest — первый шаг на пути к грамотному тестированию кода. В отличие от встроенного модуля unittest, pytest требует отдельной установки, но предоставляет значительно более мощный и гибкий инструментарий.
Установка выполняется стандартным способом через pip:
pip install pytest
Для проверки корректности установки выполните команду:
pytest --version
Если вы видите версию pytest, установка прошла успешно. 👍
Создание базовой структуры проекта с тестами — важный аспект организации кода. Рекомендуемая структура выглядит так:
- project_root/ — корневая директория проекта
- project_root/src/ или project_root/mypackage/ — исходный код приложения
- project_root/tests/ — директория с тестами
- project_root/tests/conftest.py — файл с общими фикстурами
Для правильной работы импортов необходимо создать файлы __init__.py в директориях с тестами, что позволит pytest корректно обнаруживать модули тестирования.
Настройка среды тестирования часто требует специфических параметров и плагинов. Для этого можно создать файл pytest.ini в корне проекта:
[pytest]
testpaths = tests
python_files = test_*.py
python_classes = Test*
python_functions = test_*
markers =
slow: marks tests as slow
integration: marks tests as integration tests
Также полезно установить дополнительные плагины, расширяющие функциональность pytest:
| Плагин | Назначение | Команда установки |
|---|---|---|
| pytest-cov | Измерение покрытия кода тестами | pip install pytest-cov |
| pytest-xdist | Параллельный запуск тестов | pip install pytest-xdist |
| pytest-mock | Создание mock-объектов | pip install pytest-mock |
| pytest-timeout | Отслеживание тайм-аутов тестов | pip install pytest-timeout |
Алексей Петров, Lead Python Developer
Помню, как моей команде поручили разработать микросервис для обработки платежей. Мы писали код в спешке, без тестов, думая: "Напишем потом". В пятницу вечером выкатили в продакшн — и понеслось. Посыпались баги, серверы падали, клиенты теряли деньги.
За выходные пришлось переписать систему с нуля, но уже с полным покрытием pytest-тестами. Создали фикстуры для эмуляции платежного шлюза, параметризованные тесты для проверки разных сценариев, настроили CI/CD для автоматического прогона перед каждым деплоем.
Результат? За следующий месяц — ни одного инцидента. Руководство выделило бонусы команде, а тестирование с pytest стало обязательным стандартом для всех проектов компании. Этот опыт убедил меня: час на написание тестов экономит дни на исправление ошибок.

Создание первых тестов с pytest: структура и синтаксис
Создание тестов в pytest отличается изящной простотой по сравнению с другими фреймворками. Базовая структура теста — это обычная функция Python с префиксом test_:
# test_example.py
def test_simple_addition():
assert 1 + 1 == 2
В отличие от unittest, здесь нет необходимости создавать классы или наследовать их от базовых классов тестирования. Pytest автоматически обнаружит и запустит все функции, начинающиеся с test_.
Ключевой элемент тестирования в pytest — оператор assert. В отличие от стандартных ассертов Python, pytest предоставляет расширенную диагностику при сбоях, что упрощает отладку:
def test_complex_data_comparison():
data1 = {'key1': [1, 2, 3], 'key2': 'value'}
data2 = {'key1': [1, 2, 3], 'key2': 'different'}
assert data1 == data2 # Pytest покажет точное различие
Для более сложных тестов вы можете организовать их в классы:
class TestCalculator:
def test_addition(self):
assert 1 + 1 == 2
def test_subtraction(self):
assert 3 – 1 == 2
Организация тестов в классы помогает логически группировать связанные тесты и использовать общие методы и атрибуты.
Фикстуры (fixtures) — мощный механизм pytest для подготовки тестового окружения. Они позволяют создавать и настраивать объекты, которые можно повторно использовать в различных тестах:
# conftest.py
import pytest
@pytest.fixture
def sample_data():
return {'name': 'Test User', 'age': 30}
# test_user.py
def test_user_name(sample_data):
assert sample_data['name'] == 'Test User'
def test_user_age(sample_data):
assert sample_data['age'] == 30
Фикстуры могут иметь различные области действия:
| Область (scope) | Описание | Типичное применение |
|---|---|---|
| function (по умолчанию) | Выполняется для каждого теста | Подготовка данных для отдельного теста |
| class | Выполняется один раз для класса | Общие данные для связанных тестов |
| module | Выполняется один раз для модуля | Общий ресурс для тестов в файле |
| session | Выполняется один раз за сессию | Подключение к БД или другие тяжелые ресурсы |
Пример фикстуры с областью действия и функцией очистки:
@pytest.fixture(scope="module")
def database_connection():
# Настройка: подключение к тестовой БД
conn = create_test_db_connection()
yield conn # Предоставляем соединение тестам
# Очистка после использования
conn.close()
Запуск и анализ результатов тестирования в pytest
Запуск тестов с pytest отличается гибкостью и информативностью. Базовый запуск всех тестов в текущей директории выполняется простой командой:
pytest
Для запуска конкретных тестов можно использовать различные селекторы:
- Конкретный тестовый файл:
pytest tests/test_calculator.py - Конкретная тестовая функция:
pytest tests/test_calculator.py::test_addition - Тесты по маркерам:
pytest -m "slow"(для тестов, помеченных декоратором@pytest.mark.slow) - Тесты по ключевым словам:
pytest -k "addition"(запустит тесты, содержащие слово "addition" в имени)
Параметры запуска могут значительно улучшить информативность отчетов:
-vили--verbose: подробный режим с детальной информацией о каждом тесте-s: отображение вывода print() из тестов (по умолчанию stdout перехватывается)--tb=short: сокращенный вывод трейсбека ошибок--cov=mypackage: расчет покрытия кода тестами (требует pytest-cov)
Интерпретация результатов тестирования в pytest интуитивно понятна благодаря цветовой кодировке:
- PASS (.): тест пройден успешно
- FAIL (F): тест завершился с ошибкой (сработал assert)
- ERROR (E): произошла непредвиденная ошибка во время выполнения теста
- SKIP (s): тест пропущен (например, с декоратором
@pytest.mark.skip) - XFAIL (x): ожидаемый провал теста (с
@pytest.mark.xfail) - 🟠 XPASS (X): тест, помеченный как ожидаемый провал, но успешно прошедший
Вот пример вывода запуска тестов:
============================= test session starts ==============================
platform linux -- Python 3.10.0, pytest-7.3.1
rootdir: /home/user/projects/my_project
collected 5 items
tests/test_calculator.py ..F.s [100%]
=================================== FAILURES ===================================
________________________________ test_division _________________________________
def test_division():
> assert 1 / 0 == float('inf')
E ZeroDivisionError: division by zero
tests/test_calculator.py:15: ZeroDivisionError
=========================== short test summary info ===========================
FAILED tests/test_calculator.py::test_division
========================= 1 failed, 3 passed, 1 skipped in 0.12s =============
Для анализа покрытия кода тестами используйте плагин pytest-cov:
pytest --cov=mypackage tests/
Это даст базовую статистику по покрытию. Для более детального отчета:
pytest --cov=mypackage --cov-report=html tests/
Эта команда создаст HTML-отчет в директории htmlcov/, который показывает покрытие с точностью до строки кода. 📊
Продвинутые техники тестирования с использованием pytest
Марина Сидорова, QA Lead
В прошлом году наша команда столкнулась с классической проблемой: тесты работали на локальных машинах, но регулярно падали в CI-среде. Причина оказалась в параллельном запуске, который выявлял гонки данных.
Мы решили применить параметризованные тесты pytest с различными наборами входных данных и сценариями. Создали матрицу тестирования: разные операционные системы, версии Python, конфигурации баз данных. Для каждого случая настроили отдельные фикстуры, а с помощью маркеров разделили тесты на быстрые и медленные.
Особенно помогла техника mock-объектов для эмуляции внешних API. Вместо реальных запросов к платежному шлюзу мы использовали monkeypatch для подстановки тестовых ответов с разными сценариями: успешный платеж, отказ, таймаут.
После внедрения этих практик стабильность нашей CI-системы выросла с 68% до 99,5%. Время прогона всех тестов сократилось с 40 до 12 минут благодаря грамотной параллелизации с pytest-xdist. Главный урок: инвестиции в продвинутые техники тестирования окупаются многократно.
Параметризация тестов — одна из самых мощных функций pytest. Она позволяет запускать один и тот же тест с различными входными данными, что значительно сокращает дублирование кода:
@pytest.mark.parametrize("input_value,expected_output", [
(1, 1), # Факториал 1 равен 1
(2, 2), # Факториал 2 равен 2
(3, 6), # Факториал 3 равен 6
(4, 24), # Факториал 4 равен 24
])
def test_factorial(input_value, expected_output):
from math import factorial
assert factorial(input_value) == expected_output
Использование моков (mocks) и патчей (patches) часто необходимо для изоляции тестируемого кода от внешних зависимостей:
def test_api_call(monkeypatch):
# Создаем имитацию функции запроса к API
def mock_get_json(*args, **kwargs):
return {"status": "success", "data": [1, 2, 3]}
# Заменяем реальную функцию нашей имитацией
monkeypatch.setattr("requests.get", mock_get_json)
# Тестируем функцию, которая внутри вызывает requests.get
result = fetch_data_from_api()
assert result == [1, 2, 3]
Фикстуры с очисткой (teardown) позволяют корректно освобождать ресурсы после тестов:
@pytest.fixture
def temp_file():
# Создаем временный файл
file_path = "temp_test_file.txt"
with open(file_path, "w") as f:
f.write("Test content")
# Передаем путь к файлу тестам
yield file_path
# После завершения теста удаляем файл
import os
os.remove(file_path)
Обработка исключений — важная часть тестирования. Pytest предоставляет удобный способ проверки исключений:
def divide(a, b):
if b == 0:
raise ValueError("Division by zero")
return a / b
def test_divide_by_zero():
with pytest.raises(ValueError) as excinfo:
divide(10, 0)
assert "Division by zero" in str(excinfo.value)
Маркировка тестов помогает категоризировать их и управлять запуском:
@pytest.mark.slow
def test_complex_calculation():
# Длительный тест
pass
@pytest.mark.integration
def test_database_integration():
# Тест с реальной БД
pass
Настройка пропуска тестов при определенных условиях:
@pytest.mark.skipif(sys.version_info < (3, 8), reason="requires Python 3.8 or higher")
def test_new_feature():
# Тест для функции, доступной только в Python 3.8+
pass
Для работы с асинхронным кодом используется плагин pytest-asyncio:
import pytest
import asyncio
@pytest.mark.asyncio
async def test_async_function():
result = await my_async_function()
assert result == expected_value
Интеграция pytest в процесс разработки: автоматизация и CI/CD
Интеграция тестов в процесс непрерывной разработки (CI/CD) значительно повышает качество кода. Настроенные правильно автоматические тесты помогают выявлять проблемы на ранних стадиях, до того как они попадут в продакшн. 🔄
Для интеграции pytest в популярные CI/CD системы используйте следующие конфигурации:
| CI/CD платформа | Конфигурационный файл | Ключевые параметры |
|---|---|---|
| GitHub Actions | .github/workflows/test.yml | triggers, matrix, environment, cache |
| GitLab CI | .gitlab-ci.yml | stages, image, script, artifacts |
| Jenkins | Jenkinsfile | pipeline, stages, steps, post |
| CircleCI | .circleci/config.yml | jobs, workflows, executors |
Пример конфигурации для GitHub Actions:
name: Python Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3\.8, 3.9, 3.10]
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install pytest pytest-cov
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
- name: Test with pytest
run: |
pytest --cov=mypackage tests/ --cov-report=xml
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v1
Автоматизация запуска тестов при коммитах с помощью pre-commit хуков экономит время и предотвращает отправку нерабочего кода в репозиторий:
# .pre-commit-config.yaml
repos:
- repo: local
hooks:
- id: pytest-check
name: pytest-check
entry: pytest
language: system
pass_filenames: false
always_run: true
Установка и активация pre-commit:
pip install pre-commit
pre-commit install
Оптимизация времени выполнения тестов в CI/CD критична для быстрого получения обратной связи. Стратегии оптимизации включают:
- Параллельное выполнение с помощью pytest-xdist:
pytest -n auto - Приоритизация тестов — запуск быстрых тестов первыми:
pytest --run-fast-first - Инкрементальное тестирование — запуск только тестов, затронутых изменениями
- Кеширование результатов между запусками:
pytest --cache-clearдля сброса кеша
Интеграция с системами отслеживания ошибок (issue trackers) позволяет автоматически создавать задачи при падении тестов:
pytest --jira-xfail=XYZ-123
Для контроля регрессий полезно настроить тесты с уведомлениями в случае их неожиданного прохождения:
@pytest.mark.xfail(reason="Bug #123 – this will be fixed in next release")
def test_known_failure():
# Если этот тест внезапно начнет проходить, нужно обновить документацию
assert problematic_function() == expected_result
Определение "стоп-факторов" в пайплайне CI/CD помогает предотвратить поставку проблемного кода:
- Падение критических тестов
- Снижение покрытия кода ниже установленного порога
- Обнаружение уязвимостей безопасности
- Превышение порога сложности кода
Непрерывная интеграция тестов в процессе разработки обеспечивает стабильность и высокое качество кода, сокращая затраты на поиск и исправление ошибок в будущем.
Внедрение pytest в рабочий процесс разработки — не просто дополнение, а необходимый фундамент для создания надежного программного обеспечения. Правильно организованные тесты становятся не обузой, а опорой, позволяющей смело вносить изменения в код без страха что-то сломать. Тщательно спроектированные тесты действуют как живая документация, демонстрирующая ожидаемое поведение системы и защищающая от регрессий. Помните: время, потраченное на написание тестов сегодня, окупается десятикратно завтра за счет снижения количества багов и упрощения поддержки кода.