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

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

@potapov_me

Платформа

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

Контент

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

Компания

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

Аккаунт

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

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

·ИП Потапов К.С.·Политика конфиденциальности·
Сделано с ❤️ в России
  1. Advanced fixtures: patterns и best practices
advanced_fixtures

Advanced fixtures: patterns и best practices

Fixture factories для переиспользования. Indirect parametrization. Scope strategies: session/module/function для разных ресурсов. Request object паттерны. pytest.ini: кастомные маркеры и coverage

В этом уроке углубимся в продвинутые возможности pytest fixtures. Изучим паттерны, которые используются в production-проектах.

Цель урока: Писать гибкие, переиспользуемые fixtures.

Factory fixtures

Проблема: Иногда нужно создать несколько похожих объектов с разными параметрами.

# Плохой подход: дублирование @pytest.fixture def user1(db_connection): return create_user(db_connection, "user1@test.com") @pytest.fixture def user2(db_connection): return create_user(db_connection, "user2@test.com")

Решение: Factory fixture возвращает функцию!

# conftest.py @pytest.fixture def user_factory(db_connection): """Фабрика для создания users""" created_users = [] def _create_user(email, name="Test User"): cur = db_connection.cursor() cur.execute( "INSERT INTO users (email, name) VALUES (%s, %s) RETURNING id", (email, name) ) user_id = cur.fetchone()[0] cur.close() created_users.append(user_id) return user_id yield _create_user # Cleanup: можно удалить всех созданных users # (но rollback в db_connection это сделает) # test_with_factory.py def test_multiple_users(user_factory): """Создаём несколько users одной фикстурой""" alice = user_factory("alice@test.com", "Alice") bob = user_factory("bob@test.com", "Bob") charlie = user_factory("charlie@test.com", "Charlie") assert alice != bob != charlie

Fixture parametrization

Идея: Запустить один тест с разными fixtures.

# conftest.py @pytest.fixture(params=["postgresql", "mysql", "sqlite"]) def database_type(request): """Тестируем с разными БД""" return request.param def test_with_different_databases(database_type): """Тест запустится 3 раза: для postgresql, mysql, sqlite""" print(f"Testing with {database_type}") assert database_type in ["postgresql", "mysql", "sqlite"]

Запуск:

pytest test.py -v # test_with_different_databases[postgresql] PASSED # test_with_different_databases[mysql] PASSED # test_with_different_databases[sqlite] PASSED

Практический пример:

@pytest.fixture(params=[True, False]) def completed_status(request): return request.param def test_task_with_different_status(client, completed_status): """Тестируем создание задачи с completed=True и False""" response = client.post( "/tasks", json={"title": "Test", "completed": completed_status} ) assert response.status_code == 201 assert response.json["completed"] == completed_status

Request object

request — специальный объект pytest с метаданными о тесте.

@pytest.fixture def smart_fixture(request): """Фикстура знает кто её вызывает""" test_name = request.node.name print(f"Called by test: {test_name}") # Можно передать параметры через indirect if hasattr(request, "param"): print(f"Parameter: {request.param}") yield test_name def test_example(smart_fixture): assert "test_example" in smart_fixture

Indirect parametrization:

@pytest.fixture def user_with_tasks(request, db_connection): """Создаёт user с N задачами""" task_count = request.param # Получаем параметр user_id = create_user(db_connection, f"user{task_count}@test.com") for i in range(task_count): create_task(db_connection, user_id, f"Task {i}") return user_id @pytest.mark.parametrize("user_with_tasks", [1, 5, 10], indirect=True) def test_user_task_count(user_with_tasks, db_connection): """Тест запустится 3 раза: с 1, 5, 10 задачами""" cur = db_connection.cursor() cur.execute("SELECT COUNT(*) FROM tasks WHERE user_id=%s", (user_with_tasks,)) count = cur.fetchone()[0] # count будет 1, потом 5, потом 10 assert count in [1, 5, 10] cur.close()

Fixture scope strategies

Scopes:

  • function (default) — новая фикстура для каждого теста
  • class — одна фикстура для всех тестов в классе
  • module — одна фикстура для всего файла
  • session — одна фикстура для всего запуска
@pytest.fixture(scope="session") def db_schema(): """Создаём схему БД один раз для всей сессии""" conn = psycopg2.connect(...) cur = conn.cursor() cur.execute("CREATE TABLE IF NOT EXISTS users (...)") conn.commit() cur.close() conn.close() yield # Cleanup после всех тестов @pytest.fixture(scope="module") def test_data(db_connection): """Создаём данные один раз для модуля""" user_id = create_user(db_connection, "module_user@test.com") return user_id @pytest.fixture(scope="function") def isolated_user(db_connection): """Новый user для каждого теста""" return create_user(db_connection, "test@example.com")

Когда использовать:

  • session — дорогие операции (запуск Docker, создание схемы)
  • module — данные которые не меняются между тестами
  • function — когда нужна изоляция

autouse: автоматические fixtures

autouse=True — фикстура выполняется автоматически для всех тестов.

@pytest.fixture(scope="function", autouse=True) def reset_db(db_connection): """Очищаем БД перед каждым тестом""" cur = db_connection.cursor() cur.execute("TRUNCATE tasks, users RESTART IDENTITY CASCADE") db_connection.commit() cur.close() yield def test_something(): """БД уже очищена, не нужно указывать reset_db""" pass

Осторожно: autouse может замедлить тесты. Используйте только когда нужно.

Зависимости между fixtures

Fixtures могут зависеть друг от друга:

@pytest.fixture def db_connection(): conn = psycopg2.connect(...) yield conn conn.rollback() conn.close() @pytest.fixture def user(db_connection): """Зависит от db_connection""" return create_user(db_connection, "user@test.com") @pytest.fixture def task(db_connection, user): """Зависит от db_connection И user""" return create_task(db_connection, user, "Test task") def test_task_exists(task, db_connection): """Автоматически получаем: db_connection → user → task""" cur = db_connection.cursor() cur.execute("SELECT title FROM tasks WHERE id=%s", (task,)) assert cur.fetchone()[0] == "Test task" cur.close()

Conditional fixtures с pytest.skip

@pytest.fixture def postgres_only(db_connection): """Фикстура только для PostgreSQL""" cur = db_connection.cursor() cur.execute("SELECT version()") version = cur.fetchone()[0] cur.close() if "PostgreSQL" not in version: pytest.skip("This test requires PostgreSQL") yield def test_postgres_specific_feature(postgres_only, db_connection): """Тест пропустится если не PostgreSQL""" cur = db_connection.cursor() cur.execute("SELECT version()") # ... postgres-specific test cur.close()

Best Practices

1. Именуйте fixtures понятно

# ❌ Плохо @pytest.fixture def f1(): pass # ✅ Хорошо @pytest.fixture def authenticated_user(): pass

2. Используйте conftest.py для общих fixtures

tests/
  conftest.py        # Общие fixtures для всех тестов
  test_api.py
  unit/
    conftest.py      # Fixtures только для unit тестов
    test_models.py
  integration/
    conftest.py      # Fixtures только для integration
    test_api_db.py

3. Не злоупотребляйте autouse

# ❌ Плохо: autouse для редко используемой фикстуры @pytest.fixture(autouse=True) def expensive_setup(): time.sleep(5) # Замедлит ВСЕ тесты # ✅ Хорошо: явно указываем где нужно @pytest.fixture def expensive_setup(): time.sleep(5) def test_that_needs_it(expensive_setup): pass

4. Документируйте сложные fixtures

@pytest.fixture def blog_with_data(db_connection): """ Создаёт блог с предзаполненными данными: - 3 users - 2 posts на каждого user - 1 comment на каждый post Returns: dict: {"users": [id1, id2, id3], "posts": [id1, id2, ...]} """ # ...

Что вы узнали

✅ Factory fixtures — создание множества объектов ✅ Fixture parametrization — запуск тестов с разными fixtures ✅ Request object — метаданные о тесте ✅ Scope strategies — function, module, session ✅ autouse — автоматическое выполнение fixtures ✅ Зависимости — fixtures могут зависеть друг от друга ✅ Best practices — именование, структура, документация

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

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

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

Далее: Production CI/CD и quality gates