🏷️ Маркеры и структура: запускаем нужные тесты
⚠️ Урок устарел. Свежая версия в курсе pytest-basics: Маркеры: запускаем нужные тесты.
Зачем это нужно
🐌 До: всегда гоняем всё — 10+ минут ожидания
⚡ После: запускаем ровно нужные тесты — 30 секунд
✅ Маркеры для категорий (slow/integration/api/bugfix)
✅ pytest.ini для удобного запуска в CI и IDE
✅ Структура папок + conftest.py без хаоса
Маркеры: умный запуск тестов
import pytest
import sys
@pytest.mark.slow # 🐌 Долгие (>1 с)
def test_performance():
...
@pytest.mark.integration # 🏗️ БД/сервисы
def test_database_operations():
...
@pytest.mark.external_api # 🌐 Внешний API
def test_payment_gateway():
...
@pytest.mark.skip(reason="Ждём фикс от API") # ⏸️ Временно отключено
def test_broken_external_service():
...
@pytest.mark.skipif(sys.version_info < (3, 9), reason="Требует Python 3.9+")
def test_new_syntax():
...pytest.ini:
[pytest]
markers =
slow: медленные тесты (deselect with -m "not slow")
integration: тесты с БД или сервисами
external_api: вызовы внешних API
bugfix: тесты для исправленных баговВажно: без этих объявлений pytest будет предупреждать об неизвестных маркерах.
Умный запуск
pytest -m "not slow" # Только быстрые
pytest -m integration # Только интеграционные
pytest -m bugfix # Багфиксы
pytest -rs # Показывать пропущенныеСтруктура без хаоса
Простая структура
tests/
test_todo.py
test_user.py
conftest.pyРасширенная структура и иерархия conftest
tests/
unit/ # 🚀 Быстрые проверки (<100ms)
test_todo.py
test_user.py
conftest.py # 🔧 Фикстуры только для unit
integration/ # 🏗️ Медленные/сеть/БД
test_database.py
test_api.py
conftest.py # 🔧 Фикстуры только для integration
conftest.py # 🔧 Общие фикстуры для всех тестовConftest.py: общие фикстуры и область видимости
import pytest
from src.task_manager import TaskManager
@pytest.fixture
def sample_tasks():
return ["task1", "task2"]
@pytest.fixture
def empty_task_manager():
return TaskManager()tests/unit/conftest.py (виден только для unit-тестов в этой папке):
import pytest
@pytest.fixture
def mock_database():
return MockDatabase() # моки только для unit-тестовtests/integration/conftest.py (виден только для integration):
import pytest
from myapp.db import create_connection
@pytest.fixture
def db_connection():
conn = create_connection(testing=True)
yield conn
conn.close()Pytest сам подхватывает conftest по иерархии: общий tests/conftest.py доступен всем, вложенные — только своим подпапкам. Это убирает дублирование и исключает ручные импорты фикстур.
Практика: размечаем и структурируем
import pytest, sys
@pytest.mark.unit # 🚀 Быстрый unit-тест
def test_fast_unit():
pass
@pytest.mark.integration # 🏗️ Интеграция с БД
def test_database_query():
pass
@pytest.mark.external_api # 🌐 Внешний API
@pytest.mark.slow # 🐌 Долгий
def test_external_payment_api():
pass
@pytest.mark.skip(reason="База в maintenance")
def test_database_maintenance():
passКоманды для повседневного запуска
pytest -m "not slow and not integration" --tb=short # Разработка
pytest -m "not external_api" --junitxml=report.xml # CI без внешних API
pytest --durations=10 -m "slow" # Поиск медленных
pytest tests/unit/ -v # Только unitРеальные CI/CD сценарии
Пример GitHub Actions с раздельными джобами:
jobs:
unit-tests:
runs-on: ubuntu-latest
steps:
- run: pytest -m "not slow and not integration" --junitxml=unit-report.xml
integration-tests:
runs-on: ubuntu-latest
needs: unit-tests
steps:
- run: pytest -m integration --junitxml=integration-report.xml❌ Анти-паттерн: хаос в тестах
tests/
test_everything.py # 1000 строк вперемешку
test_utils.py # Непонятно, что проверяется
helpers.py # Фикстуры разбросаныРезультат: сложно найти нужный тест, дубли фикстур, на CI гоняются все тесты всегда.
Конфигурация для команды: pytest.ini vs pyproject.toml
Проблема: Каждый член команды запускает pytest со своими флагами. Результаты отличаются локально и в CI.
Решение: Единая конфигурация в pytest.ini или pyproject.toml.
Вариант 1: pytest.ini (классика)
# pytest.ini в корне проекта
[pytest]
# Где искать тесты
testpaths = tests
# Какие файлы считать тестами
python_files = test_*.py *_test.py
# Какие классы считать тестовыми
python_classes = Test*
# Какие функции считать тестами
python_functions = test_*
# Дефолтные флаги для всех запусков
addopts =
-v
--strict-markers
--tb=short
--cov=src
--cov-report=term-missing
--cov-fail-under=70
# Регистрация маркеров (обязательно с --strict-markers)
markers =
slow: медленные тесты (deselect with '-m "not slow"')
integration: тесты с БД или сервисами
external_api: вызовы внешних API
unit: быстрые unit-тесты
bugfix: тесты для исправленных баговВариант 2: pyproject.toml (современный)
# pyproject.toml в корне проекта
[tool.pytest.ini_options]
testpaths = ["tests"]
python_files = ["test_*.py", "*_test.py"]
python_classes = ["Test*"]
python_functions = ["test_*"]
addopts = [
"-v",
"--strict-markers",
"--tb=short",
"--cov=src",
"--cov-report=term-missing",
"--cov-fail-under=70",
]
markers = [
"slow: медленные тесты (deselect with '-m \"not slow\"')",
"integration: тесты с БД или сервисами",
"external_api: вызовы внешних API",
"unit: быстрые unit-тесты",
"bugfix: тесты для исправленных багов",
]Что даёт конфигурация?
До конфигурации:
# Каждый раз вводим вручную
pytest -v --strict-markers --cov=src --cov-report=term-missing tests/После конфигурации:
# Просто pytest — все флаги подхватываются из конфига
pytest
# Переопределяем при необходимости
pytest -q # тихий режим вместо -v
pytest --no-cov # без coverageПрактика: создайте конфигурацию
Задача: Настройте pyproject.toml для своего проекта.
# Если у вас уже есть pyproject.toml — добавьте секцию
# Если нет — создайте файл
cat >> pyproject.toml << 'EOF'
[tool.pytest.ini_options]
testpaths = ["tests"]
addopts = [
"-v",
"--strict-markers",
"--tb=short",
]
markers = [
"slow: медленные тесты",
"integration: интеграционные тесты",
"unit: unit-тесты",
]
EOF
# Проверьте
pytest --markers # должны увидеть ваши маркеры
pytest # запуск с дефолтными флагами⚡ Параллельный запуск с pytest-xdist
Проблема: 1000 тестов по 0.1 секунды = 100 секунд ожидания. Ваш компьютер простаивает на 75%.
Решение: pytest-xdist запускает тесты параллельно на всех ядрах процессора.
Установка и базовое использование
# Установка
pip install pytest-xdist
# Автоматически использует все ядра
pytest -n auto
# Или явно укажите количество процессов
pytest -n 4
# С coverage (работает корректно)
pytest -n auto --cov=src
# С маркерами
pytest -n auto -m "not slow"Экономия времени (реальные цифры)
# Последовательный запуск (1 ядро)
pytest tests/
# 1000 тестов × 0.1 сек = 100 секунд
# Параллельный запуск (4 ядра)
pytest -n 4 tests/
# 1000 тестов ÷ 4 ядра × 0.1 сек = 25 секунд
# Автоматически (8 ядер на M1/M2 Mac)
pytest -n auto tests/
# 1000 тестов ÷ 8 ядер × 0.1 сек = 12.5 секунд
# ⚡ 8x ускорение!Когда использовать xdist
✅ Используйте:
- Unit-тесты (независимые, быстрые)
- Большое количество тестов (100+)
- Разработка (локальный запуск)
- CI/CD (ускорение пайплайна)
❌ Не используйте:
- Тесты с общим состоянием (БД без изоляции)
- Тесты с race conditions
- Один тест занимает 90% времени (бутылочное горлышко)
Распределение тестов (loadscope vs loadfile)
# По умолчанию: loadfile (файлы распределяются по процессам)
pytest -n 4
# loadscope: тесты одного класса идут на один процесс
pytest -n 4 --dist loadscope
# loadgroup: группировка по маркерам
pytest -n 4 --dist loadgroupДобавьте в конфигурацию
# pyproject.toml
[tool.pytest.ini_options]
addopts = [
"-n auto", # Всегда параллельно
"-v",
"--strict-markers",
]
# Или для CI:
# addopts = ["-n 4"] # Фиксированное количествоЧастые вопросы
Вопрос 1: Почему тесты падают при -n auto?
Ответ: Проверьте изоляцию:
- Тесты используют общую БД без транзакций?
- Модифицируют глобальные переменные?
- Пишут в одни файлы без уникальных имён?
Вопрос 2: Тесты стали медленнее с -n auto. Почему?
Ответ: Overhead при запуске процессов. Если тестов < 10 и они быстрые (< 0.01s), параллелизм не даст выигрыша.
Вопрос 3: Работает ли coverage с xdist?
Ответ: Да! pytest -n auto --cov=src работает корректно. Coverage собирается из всех процессов.
Чеклист организации
- Маркеры расставлены: slow/integration/api/bugfix
- Логическая структура: unit/integration + общий
conftest.py - Создана конфигурация (pytest.ini или pyproject.toml)
- Маркеры зарегистрированы с
--strict-markers - Установлен pytest-xdist для параллельного запуска
- Тесты изолированы (проходят с
-n auto) - Понимаете, какие тесты гонять локально, какие — в пайплайне
Запускайте только то, что нужно, параллельно и экономьте время команды. 🚀