Что такое фикстуры (fixtures) в pytest?

Ответ

Фикстуры в pytest — это функции, которые предоставляют тестовые данные, состояние или ресурсы, автоматически выполняя настройку (setup) и очистку (teardown). Они объявляются декоратором @pytest.fixture.

Базовый пример:

import pytest

@pytest.fixture
def database_connection():
    # Setup: создание соединения
    conn = create_connection("localhost", "test_db")
    yield conn  # Передача ресурса тесту
    # Teardown: закрытие соединения (выполняется после теста)
    conn.close()

def test_user_insert(database_connection):
    result = database_connection.execute("INSERT INTO users VALUES ('alice')")
    assert result.rowcount == 1

Области видимости (scope):

@pytest.fixture(scope="function")   # По умолчанию — для каждой функции
@pytest.fixture(scope="class")      # Один раз для класса тестов
@pytest.fixture(scope="module")     # Один раз для модуля
@pytest.fixture(scope="session")    # Один раз за сессию тестирования

Зависимости между фикстурами:

@pytest.fixture
def config():
    return {"host": "localhost", "port": 5432}

@pytest.fixture
def db_connection(config):  # Фикстура использует другую фикстуру
    return Database(host=config["host"], port=config["port"])

Параметризованные фикстуры:

@pytest.fixture(params=["sqlite", "postgres", "mysql"])
def database(request):
    if request.param == "sqlite":
        return SQLiteDatabase(":memory:")
    elif request.param == "postgres":
        return PostgresDatabase("localhost")
    # Тест запустится трижды с разными базами данных

def test_query(database):
    assert database.query("SELECT 1") == 1

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

  • Тестовые данные — подготовка объектов моделей
  • Внешние зависимости — моки API, временные файлы
  • Контекст выполнения — настройка окружения (dev/staging)
  • Очистка ресурсов — удаление временных данных после тестов

Автоматическое использование через conftest.py:

# conftest.py (фикстуры доступны во всех тестах директории)
import pytest

@pytest.fixture(autouse=True)  # Автоматически для каждого теста
def log_test_start():
    print("n--- Test started ---")
    yield
    print("--- Test finished ---")

Ответ 18+ 🔞

А, фикстуры в pytest! Ну это ж, блядь, святая святых, основа основ, как табуретка под жопой, когда пьешь чай на кухне. Без неё вроде и можно, но неудобно, сука, и всё время падаешь.

Смотри, фикстура — это такая функция-обслуга, которая тебе всё приготовит, подаст, а потом, когда ты нажрался, ещё и посуду за тобой помоет. Объявляется через @pytest.fixture. Вот смотри, элементарщина:

import pytest

@pytest.fixture
def database_connection():
    # Это setup — накрываешь на стол
    conn = create_connection("localhost", "test_db")
    yield conn  # А это — "на, жри, чмошник!"
    # А это teardown — убираешь за ним говно
    conn.close()

def test_user_insert(database_connection):
    result = database_connection.execute("INSERT INTO users VALUES ('alice')")
    assert result.rowcount == 1

Видишь магию? yield — это гениально. Всё до него — подготовка, всё после — уборка трупов. Красота, ёпта!

А ещё у них, у этих фикстур, есть, блядь, области видимости, как у кота, который метит углы. Можно настроить, чтобы она один раз на всю тусовку работала, а не для каждого теста по новой городить:

@pytest.fixture(scope="function")   # По умолчанию — новый горшок на каждый пердеж
@pytest.fixture(scope="class")      # Один горшок на всю семью (класс)
@pytest.fixture(scope="module")     # Один сортир на весь подъезд (модуль)
@pytest.fixture(scope="session")    # Одна выгребная яма на всю деревню (сессию)

Экономия ресурсов — овердохуищная!

Но самое весёлое начинается, когда они друг на друга ссылаются, как алкоголики в подворотне. Одна фикстура может зависеть от другой:

@pytest.fixture
def config():
    return {"host": "localhost", "port": 5432}

@pytest.fixture
def db_connection(config):  # Смотри-ка, сука, взяла у соседки сахар!
    return Database(host=config["host"], port=config["port"])

Pytest сам разберётся, в каком порядке их вызывать, чтобы не получилось, что база данных требует конфиг, которого ещё нет. Умный, блядь, как советский холодильник.

А если хочешь поиздеваться над тестами и прогнать их по всем мыслимым и немыслимым сценариям, есть параметризованные фикстуры. Это как дать дегустатору три сорта водки, а он должен выбрать лучший, но в итоге всё равно свалится под стол.

@pytest.fixture(params=["sqlite", "postgres", "mysql"])
def database(request):
    if request.param == "sqlite":
        return SQLiteDatabase(":memory:")
    elif request.param == "postgres":
        return PostgresDatabase("localhost")
    # Тест запустится трижды, и каждый раз будет охуевать от новой базы

def test_query(database):
    assert database.query("SELECT 1") == 1

И где это всё применяется? Да везде, сука! Подготовить тестовые данные — фикстура. Поднять мок API — фикстура. Настроить окружение, почистить за собой бардак — всё она, хитрая жопа.

А если хочешь, чтобы какая-то фикстура работала автоматом, без твоего ведома, как теща в твоём холодильнике, — положи её в conftest.py и поставь autouse=True.

# conftest.py (фикстуры тут видны всем тестам в папке и ниже)
import pytest

@pytest.fixture(autouse=True)  # Автоматически впендюрится в каждый тест
def log_test_start():
    print("n--- Test started ---")
    yield
    print("--- Test finished ---")

Вот и всё, вротберунчик. Фикстуры — это не просто синтаксический сахар, это целый кондитерский цех, который печёт торты для твоих тестов. Главное — не переборщить с зависимостями, а то получится такая паутина, что сам Чиполлино в ней запутается, блядь.