Как в pytest выполнить очистку ресурсов после завершения теста?

«Как в pytest выполнить очистку ресурсов после завершения теста?» — вопрос из категории Тестирование, который задают на 10% собеседований Python Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

В pytest для управления жизненным циклом ресурсов (например, закрытие соединений с БД, удаление временных файлов) используются фикстуры. Очистка (teardown) реализуется двумя основными способами.

1. Использование yield (рекомендуемый способ)

Это наиболее распространенный и читаемый подход. Код до yield является setup-частью, а код после yieldteardown-частью, которая гарантированно выполнится после завершения теста.

Пример с временным файлом:

import pytest
import os

@pytest.fixture
def temp_file():
    # Setup: создаем файл
    file_path = "test_temp_file.txt"
    with open(file_path, "w") as f:
        f.write("hello")

    yield file_path  # Передаем ресурс в тест

    # Teardown: удаляем файл
    print(f"nCleaning up {file_path}...")
    os.remove(file_path)

def test_read_temp_file(temp_file):
    with open(temp_file, "r") as f:
        assert f.read() == "hello"

2. Использование request.addfinalizer

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

Пример:

import pytest

class DatabaseConnection:
    def __init__(self, name):
        self.name = name
        self.is_open = True
        print(f"nDB '{self.name}' connection opened.")

    def close(self):
        self.is_open = False
        print(f"nDB '{self.name}' connection closed.")

@pytest.fixture
def db_conn(request):
    conn = DatabaseConnection("main_db")

    # Регистрируем функцию, которая будет вызвана для очистки
    request.addfinalizer(conn.close)

    return conn

def test_db_operations(db_conn):
    assert db_conn.is_open

Ключевое различие: yield — это синтаксический сахар, который более нагляден для простого сценария "setup/teardown". addfinalizer предоставляет больше гибкости, но немного усложняет код.