Перейти к основному контенту
Tech Path Finder
КурсыИнтервьюКод-ревьюБлог
Tech Path Finder

Персонализированный путеводитель в IT. Квизы, мок-интервью, код ревью и аналитика прогресса.

@potapov_me

Платформа

  • Курсы
  • Прогресс
  • Мок-интервью
  • Код ревью
  • Тренажёр переговоров
  • Закладки

Контент

  • Блог
  • Главная
  • Обратная связь

Компания

  • О проекте
  • Тарифы
  • Условия использования
  • Конфиденциальность
  • Согласие на обработку данных
  • Cookie
  • Реквизиты

Аккаунт

  • Войти
  • Зарегистрироваться
  • Профиль

© 2026 Tech Path Finder. Все права защищены.

·ИП Потапов К.С.·Политика конфиденциальности·
Сделано с ❤️ в России
  1. Fixtures на практике
fixtures_in_practice

Fixtures на практике

Устраняем дублирование кода: создаём тестовые данные, моки и подключения один раз, используем во всех тестах с правильным cleanup

⚠️ Урок устарел. Свежая версия в курсе pytest-basics: Fixtures: создаём данные один раз.

Фикстуры: профессиональная организация тестов

🔄 До: 50+ строк дублирования, ручной cleanup, 15 минут на рефакторинг 10 тестов
✅ После: 0 дублирования, автоматический cleanup, 2 минуты на изменение во всех тестах

✅ Устраняем дублирование — данные создаются один раз, используются везде
✅ Гарантируем cleanup — ресурсы освобождаются даже при падении тестов
✅ Упрощаем поддержку — меняем в одном месте, а не в 20 тестах

Проблема: дублирование кода

# ❌ ДУБЛИРОВАНИЕ: одинаковый код в каждом тесте def test_add_to_empty_list(): tasks = [] # Дублирование result = add_task(tasks, "task1") # Дублирование assert result == ["task1"] def test_add_to_existing_list(): tasks = ["existing"] # Дублирование result = add_task(tasks, "new") # Дублирование assert result == ["existing", "new"] def test_task_priority(): tasks = [] # Дублирование task = add_task(tasks, "important", priority=10) # Дублирование assert task.priority == 10 # 50+ строк однотипного setup в реальных проектах

Решение: фикстуры убирают дублирование

import pytest # ✅ ФИКСТУРЫ: переиспользуемые компоненты @pytest.fixture def empty_tasks(): return [] @pytest.fixture def sample_tasks(): return ["task1", "task2"] @pytest.fixture def task_with_high_priority(): return Task("important", priority=10) def test_add_to_empty_list(empty_tasks): result = add_task(empty_tasks, "task1") assert len(result) == 1 def test_add_to_existing_list(sample_tasks): result = add_task(sample_tasks, "new task") assert result == ["task1", "task2", "new task"] def test_task_priority(task_with_high_priority): assert task_with_high_priority.priority == 10

Скоупы: контролируем время жизни

import pytest @pytest.fixture(scope="function") # 🚀 По умолчанию: новый для каждого теста def fresh_database(): db = Database() db.connect() yield db db.disconnect() @pytest.fixture(scope="class") # 🎯 Один раз на класс тестов def api_client(): client = APIClient() client.authenticate() return client @pytest.fixture(scope="module") # 🏗️ Один раз на файл def docker_container(): container = start_postgres_container() yield container container.stop() @pytest.fixture(scope="session") # 🌐 Один раз на всю сессию def redis_connection(): redis = Redis() redis.flushall() return redis

Yield: setup и teardown вместе

yield — это «return + cleanup в одном месте».
Setup — всё, что до yield: создаём файл, открываем соединение, запускаем контейнер.
Teardown — всё, что после yield: удаляем файл, закрываем соединение, останавливаем контейнер.
Teardown выполнится даже если тест упал — идеальный способ гарантировать освобождение ресурсов.

import pytest from pathlib import Path import shutil @pytest.fixture def temporary_file(): path = Path("/tmp/test.txt") path.write_text("test data") # Setup yield path path.unlink() # Teardown @pytest.fixture def database_transaction(): conn = create_connection() tx = conn.begin() try: yield conn finally: tx.rollback() # 🛡️ Выполнится даже при падении теста conn.close()

Реалистичные примеры фикстур

Эти примеры показывают, как фикстуры решают реальные задачи: файловая конфигурация, тестовые данные в БД, моки внешних сервисов. Каждая фикстура делает setup один раз и отдаёт готовый объект тесту.

import pytest from unittest.mock import patch # Работа с файлами: создаём конфиг во временной директории @pytest.fixture def config_file(tmp_path): config_path = tmp_path / "config.yaml" config_path.write_text("debug: true\nport: 8000") return config_path # Работа с БД: создаём пользователя и отдаём его id @pytest.fixture def test_user(db_connection): user_id = db_connection.execute( "INSERT INTO users (email) VALUES (?) RETURNING id", ("test@example.com",) ).fetchone()[0] return user_id # Моки внешних сервисов: подменяем платежный шлюз @pytest.fixture def mock_payment_gateway(): with patch("app.services.payment_gateway.charge") as mock: mock.return_value = {"status": "success", "id": "ch_123"} yield mock

Фабрики, autouse и зависимости

Фабрики дают гибкие тестовые данные, autouse включает настройку для всех тестов без явного упоминания, зависимости позволяют строить фикстуры друг на друге.

import pytest # ✅ Фабрика для гибкого создания данных @pytest.fixture def task_factory(): def _create_task(text="default task", priority=1, done=False): return Task(text=text, priority=priority, done=done) return _create_task # ✅ Автоиспользование для глобальной настройки без явного указания в тестах @pytest.fixture(autouse=True) def reset_settings(): original_debug = settings.DEBUG settings.DEBUG = True yield settings.DEBUG = original_debug # ✅ Зависимость фикстур: user строится на базе database @pytest.fixture def database(): return Database() @pytest.fixture def user(database): return database.create_user("test")

conftest.py: общие фикстуры

Иерархия conftest наглядно:

tests/
├── conftest.py           # 🌍 Доступно всем тестам
├── unit/
│   ├── conftest.py       # 🚀 Только unit
│   └── test_todo.py
└── integration/
    ├── conftest.py       # 🏗️ Только integration
    └── test_database.py

Структура:

tests/
  unit/
    test_todo.py
    test_user.py
  integration/
    test_database.py
  conftest.py

tests/conftest.py:

import pytest from src.app import create_app from src.database import Database @pytest.fixture def app(): """Тестовое приложение""" return create_app(testing=True) @pytest.fixture def client(app): """HTTP клиент""" return app.test_client() @pytest.fixture def db_session(): """Сессия БД с автоочисткой""" db = Database(testing=True) db.create_tables() yield db db.drop_tables()

conftest.py — стандартное имя, которое pytest находит автоматически. Любые фикстуры внутри доступны всем тестам в этой папке и вложенных, без явного импорта. Это соглашение, но для автоподхвата фикстур использовать другое имя нельзя.

Практика

Попробуйте решить задачи сами, а затем сверяйтесь с примерами ниже.

  • Задача 1: Устраните дублирование в тестах БД (автоматическое подключение/отключение, cleanup).
  • Задача 2: Создайте фабрику для задач, чтобы легко менять текст/приоритет/статус без копипасты.

Продвинутые паттерны и решения

Это готовое решение задачи «Создайте фабрику для тестовых данных».

@pytest.fixture def task_factory(): def _create_task(text="default task", priority=1, done=False): return Task(text=text, priority=priority, done=done) return _create_task def test_task_creation(task_factory): task = task_factory(text="Learn pytest", priority=1, done=False) assert task.text == "Learn pytest" def test_task_completion(task_factory): task = task_factory(text="Write tests", priority=5, done=False) task.mark_done() assert task.done is True def test_task_high_priority(task_factory): task = task_factory(text="Critical", priority=10) assert task.priority == 10 def test_task_defaults(task_factory): task = task_factory() # Используем значения по умолчанию assert task.text == "default task"

Фабрика = один «конструктор данных» вместо множества отдельных фикстур: меняем аргументы — получаем нужный объект без копипасты.

Решение практики для БД

import pytest @pytest.fixture def database(): db = Database() db.connect() try: yield db finally: db.disconnect() # 🛡️ Гарантированное отключение def test_create_user(database): user_id = database.create_user("alice") assert user_id == 1 def test_delete_user(database): database.create_user("alice") database.delete_user("alice") assert not database.user_exists("alice") # 🎯 Убрали дублирование и добавили cleanup

💥 Реальные последствия анти-паттернов

import pytest shared_users = [] @pytest.fixture(scope="session") def global_users(): return shared_users def test_user_creation(global_users): global_users.append("alice") # 🚨 Меняем глобальное состояние def test_user_count(global_users): assert len(global_users) == 0 # 💥 Падает из-за предыдущего теста
  • Недетерминированные тесты: проходят/падают в зависимости от порядка
  • Сложно отладить: 100 тестов — непонятно, кто меняет состояние
  • Flaky в CI/CD: иногда проходят, иногда нет

🚀 Шпаргалка: фикстуры в действии

Когда использовать каждый scope

  • function — тестовые данные, моки, изолированные объекты
  • class — общая конфигурация для группы тестов
  • module — тяжёлые ресурсы (Docker, тестовая БД)
  • session — кеш, глобальные конфиги, подключения к внешним сервисам

Паттерны для частых сценариев

@pytest.fixture def sample_data(): return {"key": "value"} @pytest.fixture def temp_db(): db = setup() yield db db.cleanup() @pytest.fixture def user_factory(): defaults = {"name": "Test", "email": "test@example.com"} return lambda **kwargs: User(**{**defaults, **kwargs}) @pytest.fixture(autouse=True) def test_mode(): settings.TESTING = True yield settings.TESTING = False

Команды для отладки фикстур

pytest --fixtures # показать все фикстуры pytest tests/test_example.py --fixtures # фикстуры файла pytest -v -s tests/test_example.py::test_name # вывод во время теста

Проверьте понимание

Вопрос 1: В 10 тестах дублируется создание пользователя с разными ролями. Как улучшить?
Вопрос 2: Тесты работают с временными файлами, но иногда файлы не удаляются. В чём проблема?
Вопрос 3: Фикстура БД использует scope="session", и тесты влияют друг на друга. Что делать?

💡 Ответы

  1. Сделать фабрику пользователей:
@pytest.fixture def user_factory(): def _create(role="user"): return User(role=role) return _create
  1. Использовать yield для cleanup:
@pytest.fixture def temp_file(): path = create_temp_file() yield path path.unlink()
  1. Сменить scope или очищать данные:
@pytest.fixture(scope="function") def clean_database(): db = Database() yield db db.clear_all_data()

Чеклист качественных фикстур

  • Устраняют дублирование — код не повторяется в тестах
  • Правильный scope — начинайте с function, расширяйте при необходимости
  • Гарантированный cleanup — yield/finally для ресурсов
  • Изолированное состояние — тесты не влияют друг на друга
  • Понятные имена — сразу ясно, что возвращает фикстура
  • Общие фикстуры в conftest.py — переиспользование между файлами

Поздравляю! Теперь ваши тесты чище, надёжнее и проще в поддержке. 🎯

Проверьте свои знания

Вопросы ещё не добавлены

Вопросы для этой подтемы ещё не добавлены.

Далее: Отладка тестов: понимаем ошибки и быстро чиним