Для чего в фикстурах pytest используется yield

Ответ

Использование yield в фикстурах pytest — это идиоматичный способ управления ресурсами, которые требуют явной инициализации (setup) и последующей очистки (teardown).

Этот паттерн разделяет фикстуру на три части:

  1. Setup: Код, выполняемый до yield. Здесь происходит создание и настройка ресурса (например, подключение к БД, запуск веб-драйвера, создание временного файла).
  2. Yield: Ключевое слово, которое передает созданный ресурс в тест и приостанавливает выполнение фикстуры.
  3. Teardown: Код, выполняемый после yield. Он гарантированно выполняется после завершения теста (даже если тест упал), обеспечивая корректное освобождение ресурса (закрытие соединения, удаление файла).

Пример с подключением к базе данных:

import pytest

class DatabaseConnection:
    def __init__(self, dsn):
        print(f"n[SETUP] Connecting to {dsn}...")
        self._connection = "<connection_object>"

    def query(self, sql):
        return f"Executing '{sql}'"

    def close(self):
        print("n[TEARDOWN] Closing connection...")

@pytest.fixture(scope="module")
def db_connection():
    # 1. Setup-фаза
    conn = DatabaseConnection("postgresql://user:pass@host/db")

    # 2. Передача ресурса в тест
    yield conn

    # 3. Teardown-фаза (гарантированно выполнится)
    conn.close()

def test_user_query(db_connection):
    result = db_connection.query("SELECT * FROM users")
    assert "users" in result

Этот подход является более чистой и надежной альтернативой addfinalizer, так как логика setup и teardown находится в одном месте и работает аналогично конструкции try...finally.

Ответ 18+ 🔞

Давай разберём эту тему про yield в фикстурах, а то некоторые до сих пор, блядь, как обезьяны с гранатой — код пишут, а потом ресурсы не закрывают, и сервера падают, ёпта!

Вот смотри, есть у тебя какая-нибудь штука, которая требует уборки после себя. Например, подключение к базе данных, которое ты открыл, поработал и должен закрыть. Если не закроешь — получишь утечку, и потом админ тебе, пидарас шерстяной, всю ночь будет звонить, спрашивая, кто это у него все соединения в PostgreSQL съел.

Раньше, в каменном веке, для этого использовали addfinalizer. Но это было неудобно, блядь, как ехать на велосипеде с квадратными колёсами. Логика setup и teardown была размазана по коду, и можно было легко запутаться.

А теперь есть красота — yield. Это как разбить фикстуру на три понятных куска, прямо как слоёный пирог, только без мака.

  1. Сначала готовим (Setup). Всё, что до yield. Тут ты ресурс создаёшь: базу поднимаешь, файл открываешь, браузер запускаешь.
  2. Потом отдаём и ждём (Yield). Сам yield — это волшебная пауза. Он отдаёт твой готовый ресурс тесту и замирает, пока тест работает. Представь, что ты передал другу свою игровую приставку и стоишь рядом, ждёшь, пока он поиграет.
  3. Наконец, убираем за собой (Teardown). Всё, что после yield. Этот код выполнится гарантированно, даже если тест упадёт с криком "AssertionError!" или его убьёт таймаут. Это твой шанс всё прибрать: соединение закрыть, файл удалить, браузер прибить. Как конструкция try...finally, только элегантнее, сука.

Вот смотри на живой пример. Представь, что у нас есть класс для подключения к базе, который мы сами написали, пока начальник не видел.

import pytest

class DatabaseConnection:
    def __init__(self, dsn):
        print(f"n[SETUP] Connecting to {dsn}...")
        self._connection = "<connection_object>"

    def query(self, sql):
        return f"Executing '{sql}'"

    def close(self):
        print("n[TEARDOWN] Closing connection...")

@pytest.fixture(scope="module")
def db_connection():
    # 1. Всё, что до yield — настраиваем
    conn = DatabaseConnection("postgresql://user:pass@host/db")

    # 2. Отдаём подключение тесту и замираем
    yield conn

    # 3. Всё, что после yield — прибираем (сработает ВСЕГДА)
    conn.close()

def test_user_query(db_connection):
    result = db_connection.query("SELECT * FROM users")
    assert "users" in result

Что здесь происходит? Фикстура db_connection создаёт объект подключения, говорит "на, тест, работай" (yield conn), а потом, когда тест отвалится (в хорошем смысле), она выполнит conn.close(). Даже если твой тест словит исключение посреди запроса — соединение всё равно закроется. Красота, да и только!

Это, блядь, и есть идиоматичный способ в pytest. Чисто, надёжно, и не надо изобретать велосипед с реактивным двигателем. Просто запомни: yield — это не только для генераторов, но и для того, чтобы не быть свиньёй и не засирать память открытыми ресурсами.