← Назад к вопросам

В чем разница между pytest.fixture scope session и function?

1.8 Middle🔥 181 комментариев
#Теория тестирования

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI7 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Разница между scope="session" и scope="function" в pytest

В pytest фикстуры (fixtures) — это мощный механизм для предоставления тестовых данных, состояния и зависимостей. Ключевым параметром, определяющим жизненный цикл фикстуры, является scope (область видимости). Два наиболее часто используемых значения — session и function — кардинально различаются по времени жизни и повторному использованию.

Основное отличие

  • scope="function" (значение по умолчанию): Фикстура создается заново для каждого тестового случая (test function). Это изоляция в чистом виде.
  • scope="session": Фикстура создается единожды за всю сессию выполнения тестов (т.е. за один запуск pytest). Это оптимальный способ для дорогостоящих операций.

Детальное сравнение

### scope="function" (По умолчанию)

Жизненный цикл: Создается перед выполнением каждого отдельного теста и уничтожается (или финализатор выполняется) сразу после его завершения.

Идеальный вариант использования:

  • Тесты, которые изменяют состояние объекта, полученного из фикстуры.
  • Тесты, требующие полной изоляции друг от друга.
  • Работа со свежими, чистыми данными для каждого случая (например, новый экземпляр браузера, чистая тестовая БД).

Пример:

import pytest

@pytest.fixture(scope="function")
def fresh_user():
    """Создает нового пользователя для КАЖДОГО теста."""
    user = {"name": "TestUser", "id": 1}
    print("\n> Создан пользователь (function-scope)")
    yield user
    print("> Очистка пользователя (function-scope)")

def test_user_name(fresh_user):
    assert fresh_user["name"] == "TestUser"
    fresh_user["name"] = "Modified"  # Модификация для этого теста

def test_user_id(fresh_user):
    # Здесь fresh_user — это НОВЫЙ объект, модификации из прошлого теста не видны
    assert fresh_user["id"] == 1
    assert fresh_user.get("name") == "TestUser"  # Значение по умолчанию, а не "Modified"

Вывод в консоли будет примерно таким:

> Создан пользователь (function-scope)
PASSED
> Очистка пользователя (function-scope)

> Создан пользователь (function-scope)
PASSED
> Очистка пользователя (function-scope)

Фикстура инициализировалась и завершилась дважды.

### scope="session"

Жизненный цикл: Создается один раз в начале тестовой сессии и уничтожается в самом ее конце, после выполнения всех тестов.

Идеальный вариант использования:

  • Дорогостоящие операции: установка соединения с базой данных, запуск Docker-контейнера, инициализация сложного внешнего сервиса.
  • Общие, неизменяемые ресурсы: конфигурационные файлы, глобальные настройки, кэши.
  • Действия, которые должны быть выполнены только один раз (напр., аутентификация в API).

Важное предупреждение: Тесты НЕ должны изменять общее состояние session-фикстуры, если это может повлиять на другие тесты. Для изолированных изменений лучше использовать фикстуры с меньшим scope (например, function) или создавать копии данных.

Пример:

import pytest
import sqlite3

@pytest.fixture(scope="session")
def database_connection():
    """Устанавливает соединение с БД ОДИН раз за весь прогон тестов."""
    conn = sqlite3.connect(":memory:")  # Дорогостоящая операция
    cursor = conn.cursor()
    cursor.execute("CREATE TABLE users (id INT, name TEXT);")
    cursor.execute("INSERT INTO users VALUES (1, 'Alice'), (2, 'Bob');")
    conn.commit()
    print("\n>>> Сессионное соединение с БД установлено")
    yield conn
    conn.close()
    print(">>> Сессионное соединение с БД закрыто")

def test_user_count(database_connection):
    cursor = database_connection.cursor()
    cursor.execute("SELECT COUNT(*) FROM users")
    count = cursor.fetchone()[0]
    assert count == 2

def test_user_exists(database_connection):
    # Используется то ЖЕ САМОЕ соединение, что и в предыдущем тесте
    cursor = database_connection.cursor()
    cursor.execute("SELECT name FROM users WHERE id=1")
    name = cursor.fetchone()[0]
    assert name == "Alice"

Вывод в консоли будет примерно таким:

>>> Сессионное соединение с БД установлено
PASSED
PASSED
>>> Сессионное соединение с БД закрыто

Фикстура инициализировалась и завершилась только один раз, несмотря на два теста.


Сводная таблица

Критерийscope="function"scope="session"
Время жизниОдин тестВся сессия тестов
Частота созданияДля каждого тестаОдин раз при запуске
ПроизводительностьМеньше (повторная инициализация)Выше (дорогая операция — один раз)
Изоляция тестовПолнаяПотенциальные конфликты состояний
Типичное применениеСвежие данные, изолированные окруженияОбщие подключения, конфигурация, кэш

Практические рекомендации

  1. Начинайте с scope="function". Это безопаснее и обеспечивает изоляцию. Переходите к session только при необходимости оптимизации.
  2. Используйте autouse=True для session-фикстур, когда нужно гарантированно выполнить настройку/очистку для всех тестов (например, подготовить и очистить глобальный временный каталог).
  3. Комбинируйте scope. Частая паттерн — session-фикстура создает дорогой ресурс (например, соединение с БД), а function-фикстура создает для каждого теста новую транзакцию или изолированные данные, используя это соединение.
    @pytest.fixture(scope="session")
    def db_conn():
        conn = create_expensive_connection()
        yield conn
        conn.close()
    
    @pytest.fixture(scope="function")
    def db_transaction(db_conn):  # Зависит от session-фикстуры
        transaction = db_conn.begin()
        yield transaction
        transaction.rollback()  # Откат после каждого теста для изоляции
    

Вывод: Выбор между session и function — это компромисс между производительностью и изоляцией тестов. scope="function" — ваш надежный выбор по умолчанию для стабильных, независимых тестов. scope="session" — это мощный инструмент для оптимизации, который требует аккуратного использования во избежание скрытых зависимостей между тестами.

В чем разница между pytest.fixture scope session и function? | PrepBro