В чем разница между pytest.fixture scope session и function?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Разница между 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" |
|---|---|---|
| Время жизни | Один тест | Вся сессия тестов |
| Частота создания | Для каждого теста | Один раз при запуске |
| Производительность | Меньше (повторная инициализация) | Выше (дорогая операция — один раз) |
| Изоляция тестов | Полная | Потенциальные конфликты состояний |
| Типичное применение | Свежие данные, изолированные окружения | Общие подключения, конфигурация, кэш |
Практические рекомендации
- Начинайте с
scope="function". Это безопаснее и обеспечивает изоляцию. Переходите кsessionтолько при необходимости оптимизации. - Используйте
autouse=Trueдля session-фикстур, когда нужно гарантированно выполнить настройку/очистку для всех тестов (например, подготовить и очистить глобальный временный каталог). - Комбинируйте 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" — это мощный инструмент для оптимизации, который требует аккуратного использования во избежание скрытых зависимостей между тестами.