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

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

@potapov_me

Платформа

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

Контент

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

Компания

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

Аккаунт

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

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

·ИП Потапов К.С.·Политика конфиденциальности·
Сделано с ❤️ в России
  1. Fixture factories и indirect parametrization
fixture_factories

Fixture factories и indirect parametrization

Динамическое создание фикстур, indirect для параметризации фикстур, request object паттерны

Нужно протестировать функцию с 5 разными пользователями: admin, moderator, user, guest, banned. Создавать 5 фикстур? Есть способ лучше!

Цель: Освоить fixture factories и indirect parametrization для гибкого создания тестовых данных.

Вы точно готовы?

Убедитесь, что умеете:

# Параметризация тестов @pytest.mark.parametrize("value", [1, 2, 3]) def test_values(value): assert value > 0 # Фикстуры с scope @pytest.fixture(scope="module") def database(): return connect_to_db()

Если parametrize или scope непонятны — вернитесь к предыдущим урокам.

Проблема: множество похожих фикстур

Дублирование фикстур

# ❌ ПЛОХО — 5 почти одинаковых фикстур @pytest.fixture def admin_user(): return User(name="Admin", role="admin", permissions=["all"]) @pytest.fixture def moderator_user(): return User(name="Moderator", role="moderator", permissions=["moderate"]) @pytest.fixture def regular_user(): return User(name="User", role="user", permissions=["read", "write"]) @pytest.fixture def guest_user(): return User(name="Guest", role="guest", permissions=["read"]) @pytest.fixture def banned_user(): return User(name="Banned", role="banned", permissions=[])

Проблемы:

  • ❌ Дублирование кода
  • ❌ Сложно добавить новые роли
  • ❌ Сложно изменить структуру User

Решение 1: Fixture factory

Фикстура возвращает функцию

@pytest.fixture def make_user(): """Factory для создания пользователей""" def _make_user(role="user", name=None, permissions=None): if name is None: name = f"{role.capitalize()}User" if permissions is None: permissions = { "admin": ["all"], "moderator": ["read", "write", "moderate"], "user": ["read", "write"], "guest": ["read"], "banned": [] }[role] return User(name=name, role=role, permissions=permissions) return _make_user

Использование:

def test_admin_can_delete(make_user): admin = make_user(role="admin") assert admin.can("delete") def test_guest_cannot_write(make_user): guest = make_user(role="guest") assert not guest.can("write") def test_custom_user(make_user): custom = make_user(role="custom", permissions=["special"]) assert custom.can("special")

✅ Одна фикстура, любые вариации!

Factory с cleanup

@pytest.fixture def make_database_user(database): """Factory для пользователей в БД""" created_users = [] def _make_user(**kwargs): user = User.create(database, **kwargs) created_users.append(user) return user yield _make_user # Cleanup: удаляем всех созданных пользователей for user in created_users: user.delete()

Использование:

def test_multiple_users(make_database_user): alice = make_database_user(name="Alice", age=25) bob = make_database_user(name="Bob", age=30) charlie = make_database_user(name="Charlie", age=35) assert User.count() == 3 # После теста все 3 пользователя удалятся автоматически!

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

@pytest.fixture def make_order(database): """Factory для заказов""" orders = [] def _make_order(user_id=1, items=None, status="pending"): if items is None: items = [{"product": "Laptop", "qty": 1}] order = Order.create( database, user_id=user_id, items=items, status=status, total=sum(item["qty"] * 100 for item in items) ) orders.append(order) return order yield _make_order for order in orders: order.delete() def test_order_total(make_order): order = make_order(items=[ {"product": "Laptop", "qty": 2}, {"product": "Mouse", "qty": 5}, ]) assert order.total == 700 def test_order_status_flow(make_order): order = make_order(status="pending") order.process() assert order.status == "processing"

Решение 2: Indirect parametrization

Проблема: параметризация фикстур

# Хочу запустить тест с разными пользователями @pytest.mark.parametrize("user", ["admin", "moderator", "user"]) def test_access(user): # user — это строка "admin", не объект User! assert user.can("read") # ❌ Не работает

Решение: indirect=True

@pytest.fixture def user(request): """Создаёт пользователя по роли из параметра""" role = request.param # Получаем параметр из parametrize return User(name=role.capitalize(), role=role) @pytest.mark.parametrize("user", ["admin", "moderator", "user"], indirect=True) def test_access(user): # user — это объект User! assert user.can("read") # ✅ Работает

Что происходит:

  1. @pytest.mark.parametrize("user", ["admin", ...], indirect=True)
  2. pytest передаёт "admin" в фикстуру user через request.param
  3. Фикстура создаёт User(role="admin")
  4. Тест получает объект User

✅ Тест запустится 3 раза с разными пользователями!

Множественная indirect параметризация

@pytest.fixture def user(request): role = request.param return User(role=role) @pytest.fixture def database(request): db_type = request.param return connect_to_db(db_type) @pytest.mark.parametrize("user", ["admin", "user"], indirect=True) @pytest.mark.parametrize("database", ["postgres", "mysql"], indirect=True) def test_user_database(user, database): """Тест комбинации user × database""" user.save(database) assert user.id is not None

Результат: 4 теста (2 пользователя × 2 БД)

test_user_database[postgres-admin] PASSED
test_user_database[postgres-user] PASSED
test_user_database[mysql-admin] PASSED
test_user_database[mysql-user] PASSED

Практический пример: тестирование permissions

@pytest.fixture def user(request): """Фикстура создаёт пользователя по роли""" role = request.param permissions = { "admin": ["read", "write", "delete", "admin"], "moderator": ["read", "write", "moderate"], "user": ["read", "write"], "guest": ["read"], "banned": [] } return User(role=role, permissions=permissions[role]) @pytest.mark.parametrize("user", ["admin", "moderator"], indirect=True) def test_can_moderate(user): """Админ и модератор могут модерировать""" assert user.can("moderate") or user.role == "admin" @pytest.mark.parametrize("user", ["user", "guest", "banned"], indirect=True) def test_cannot_moderate(user): """User, guest и banned не могут модерировать""" assert not user.can("moderate") @pytest.mark.parametrize("user", ["admin", "moderator", "user", "guest", "banned"], indirect=True) def test_everyone_can_read(user): """Все кроме banned могут читать""" if user.role != "banned": assert user.can("read") else: assert not user.can("read")

Request object: доступ к контексту теста

request.param — параметры из parametrize

@pytest.fixture def resource(request): param = request.param return create_resource(param)

request.node — информация о тесте

@pytest.fixture def logger(request): """Логирует информацию о тесте""" test_name = request.node.name test_file = request.node.fspath print(f"\n🧪 Running: {test_name} in {test_file}") yield print(f"✅ Finished: {test_name}")

request.config — доступ к pytest config

@pytest.fixture def base_url(request): """Получает base_url из pytest config""" return request.config.getoption("--base-url", default="http://localhost") def test_api(base_url): response = requests.get(f"{base_url}/api/users") assert response.status_code == 200

Запуск:

pytest --base-url=https://api.example.com

request.addfinalizer — альтернатива yield

@pytest.fixture def resource(request): """Фикстура с finalizer""" res = create_resource() # Регистрируем cleanup def cleanup(): res.close() request.addfinalizer(cleanup) return res

Преимущество: Можно зарегистрировать несколько finalizers:

@pytest.fixture def complex_resource(request): resource = create_resource() request.addfinalizer(lambda: resource.close()) request.addfinalizer(lambda: cleanup_files()) request.addfinalizer(lambda: send_metrics()) return resource

Комбинация: factory + indirect (bonus)

@pytest.fixture def make_user(): """Factory внутри фикстуры""" users = [] def _make(role): user = User(role=role) users.append(user) return user yield _make for user in users: user.delete() @pytest.fixture def user(request, make_user): """Indirect фикстура использует factory""" role = request.param return make_user(role) @pytest.mark.parametrize("user", ["admin", "moderator", "user"], indirect=True) def test_permissions(user): assert user.role in ["admin", "moderator", "user"] # Все пользователи удалятся после теста благодаря factory!

Что вы изучили

  • Fixture factories — фикстуры возвращают функции для создания объектов
  • indirect=True — параметризация фикстур
  • request.param — доступ к параметрам из parametrize
  • request object — node, config, addfinalizer
  • Комбинация — factory + indirect для мощных паттернов
  • Best practices — DRY для тестовых данных

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

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

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

Далее: Custom markers и strict mode