Какие уровни видимости (scope) существуют для фикстур в pytest

Ответ

В pytest параметр scope у декоратора @pytest.fixture определяет жизненный цикл фикстуры — как часто она будет создаваться и уничтожаться.

Существуют следующие уровни scope (от самого узкого к самому широкому):

  • function: (По умолчанию) Фикстура выполняется для каждой тестовой функции. Обеспечивает максимальную изоляцию тестов.
  • class: Фикстура выполняется один раз на тестовый класс.
  • module: Фикстура выполняется один раз на модуль (файл).
  • package: Фикстура выполняется один раз на пакет (директорию с __init__.py).
  • session: Фикстура выполняется один раз на всю тестовую сессию.

Как выбрать правильный scope?

Выбор зависит от ресурсоемкости инициализации и необходимости изоляции.

  • Используйте function для полной изоляции, когда каждый тест должен работать с "чистым" состоянием.
  • Используйте module или session для "тяжелых" ресурсов (например, подключение к БД, запуск Docker-контейнера), инициализация которых занимает много времени. Это значительно ускоряет выполнение тестов.

Пример (scope='module'):

import pytest

# Эта фикстура будет вызвана только один раз для всего файла
@pytest.fixture(scope='module')
def db_connection():
    print("nУстановка соединения с БД...")
    conn = create_db_connection() # Условная функция
    yield conn
    print("nЗакрытие соединения с БД...")
    conn.close()

# Оба теста будут использовать один и тот же экземпляр db_connection
def test_query1(db_connection):
    assert db_connection.execute("SELECT 1") is not None

def test_query2(db_connection):
    assert db_connection.execute("SELECT 2") is not None

Важно: фикстуры с широким scope (module, session) должны быть потокобезопасными при параллельном запуске тестов (например, с pytest-xdist).

Ответ 18+ 🔞

Давай разберём эту тему про scope в pytest, а то некоторые думают, что это про снайперские винтовки, а не про тесты. Суть в том, как часто твоя фикстура будет выёбываться и создаваться заново.

Вот какие есть уровни, от самого назойливого до самого ленивого:

  • function (стоит по умолчанию): Фикстура отрабатывает для каждого отдельного теста. Каждый тест — как будто первый день в армии, всё с чистого листа. Максимальная изоляция, но иногда овердохуища лишней работы.
  • class: Фикстура прикидывается умной и создаётся один раз на весь класс. Экономия, блядь, на спичках, но иногда работает.
  • module: Вообще красота! Фикстура выполняется один раз на файл. Запустилась в начале файла и до конца сессии терпит все твои тесты.
  • package: Для тех, кто любит порядок. Один раз на папку (пакет). Редко кто заморачивается, но жить можно.
  • session: Апогей лени! Фикстура создаётся один раз за весь прогон и потом живёт, пока все тесты не отстреляются. Идеально для чего-то тяжёлого, вроде поднятия целой инфраструктуры.

Так какую же выбрать, ёпта?

Всё просто, как три копейки:

  • function — когда тестам надо быть изолированными, чтобы они друг другу состояния не портили. Дефолтный и часто правильный выбор.
  • module / session — когда инициализация фикстуры — это такой геморрой, что проще один раз потратиться, а потом всем раздавать. Например, подключение к базе или запуск сервиса. Скорость тестов взлетает просто в небеса.

Смотри, как это выглядит на практике (scope='module'):

import pytest

# Эта штука отработает один раз на файл, а не на каждый тест
@pytest.fixture(scope='module')
def db_connection():
    print("nУстановка соединения с БД...") # Увидишь это сообщение ОДИН раз
    conn = create_db_connection() # Условная функция
    yield conn # А вот тут она отдаёт соединение всем тестам по очереди
    print("nЗакрытие соединения с БД...") # И закрывается тоже один раз в конце
    conn.close()

# Оба теста будут пить из одной и той же кружки db_connection
def test_query1(db_connection):
    assert db_connection.execute("SELECT 1") is not None

def test_query2(db_connection):
    assert db_connection.execute("SELECT 2") is not None

И главное предупреждение, чтоб потом не орать "почему не работает!": если используешь широкие scope (module, session) и гоняешь тесты параллельно (например, через pytest-xdist), убедись, что твоя фикстура не начнёт сама с собой конфликтовать в разных потоках. А то будет весело, в рот меня чих-пых.