← Назад к вопросам

Какие бывают параметры scope в fixture в pytest?

1.0 Junior🔥 181 комментариев
#Тестирование

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

# Параметры scope в pytest fixtures

Fixtures в pytest — это один из самых мощных инструментов для организации тестов. Параметр scope определяет как долго будет жить объект и сколько раз он будет переиспользован.

1. function scope (по умолчанию)

Поведение

Фиксчура создаётся перед каждой функцией и удаляется после неё.

import pytest

@pytest.fixture(scope="function")
def database():
    print("\n[Setup] Creating database connection")
    db = {"users": []}
    yield db
    print("[Teardown] Closing database")

def test_add_user_1(database):
    print("Running test 1")
    database["users"].append({"id": 1, "name": "John"})
    assert len(database["users"]) == 1

def test_add_user_2(database):
    print("Running test 2")
    database["users"].append({"id": 2, "name": "Jane"})
    # database["users"] пуст! (новое создание)
    assert len(database["users"]) == 1

# Output:
# [Setup] Creating database connection
# Running test 1
# [Teardown] Closing database
# [Setup] Creating database connection
# Running test 2
# [Teardown] Closing database

Когда использовать:

  • По умолчанию используй это
  • Каждый тест должен начинать с чистого состояния
  • Гарантирует изоляцию тестов

2. class scope

Поведение

Фиксчура создаётся один раз для всего класса и переиспользуется всеми методами класса.

@pytest.fixture(scope="class")
def api_client():
    print("\n[Setup] Initializing API client")
    client = {"authenticated": True, "token": "abc123"}
    yield client
    print("[Teardown] Closing API client")

class TestUserAPI:
    def test_get_users(self, api_client):
        print(f"Test 1: Using {api_client['token']}")
        assert api_client["authenticated"]
    
    def test_create_user(self, api_client):
        print(f"Test 2: Using {api_client['token']}")
        assert api_client["authenticated"]
    
    def test_delete_user(self, api_client):
        print(f"Test 3: Using {api_client['token']}")
        assert api_client["authenticated"]

# Output:
# [Setup] Initializing API client
# Test 1: Using abc123
# Test 2: Using abc123
# Test 3: Using abc123
# [Teardown] Closing API client

Когда использовать:

  • Дорогие операции (подключение к БД, API)
  • Когда тесты в классе независимы друг от друга
  • Осторожно: может привести к взаимозависимости тестов

3. module scope

Поведение

Фиксчура создаётся один раз для всего модуля и переиспользуется всеми тестами в модуле.

@pytest.fixture(scope="module")
def test_data():
    print("\n[Setup] Loading test data from file")
    data = {
        "users": [{"id": 1, "name": "John"}, {"id": 2, "name": "Jane"}],
        "products": [{"id": 1, "name": "Product 1"}]
    }
    yield data
    print("[Teardown] Cleaning up test data")

def test_user_count(test_data):
    print("Test 1: Checking user count")
    assert len(test_data["users"]) == 2

def test_product_count(test_data):
    print("Test 2: Checking product count")
    assert len(test_data["products"]) == 1

class TestDataProcessing:
    def test_data_not_empty(self, test_data):
        print("Test 3: In class")
        assert len(test_data["users"]) > 0

# Output:
# [Setup] Loading test data from file
# Test 1: Checking user count
# Test 2: Checking product count
# Test 3: In class
# [Teardown] Cleaning up test data

Когда использовать:

  • Большие наборы данных (не меняются во время тестов)
  • Настройка сложного окружения
  • Когда все тесты в модуле читают одни данные

4. session scope

Поведение

Фиксчура создаётся один раз для всей сессии тестирования и переиспользуется всеми тестами.

@pytest.fixture(scope="session")
def postgres_db():
    print("\n[Setup] Starting PostgreSQL container")
    # В реальном примере это docker или docker-compose
    db = {
        "host": "localhost",
        "port": 5432,
        "name": "test_db",
        "migrations_applied": True
    }
    yield db
    print("[Teardown] Stopping PostgreSQL container")

def test_db_connection(postgres_db):
    print("Test 1: Checking DB connection")
    assert postgres_db["migrations_applied"]

def test_db_query(postgres_db):
    print("Test 2: Running query")
    assert postgres_db["host"] == "localhost"

# Если запустить ещё один тест файл или класс:
# pytest test_database.py test_api.py

# Output:
# [Setup] Starting PostgreSQL container  # Один раз для всей сессии!
# Test 1: Checking DB connection
# Test 2: Running query
# [Teardown] Stopping PostgreSQL container  # В конце всего

Когда использовать:

  • Инициализация БД или контейнеров Docker
  • Один раз при запуске всех тестов
  • Экономит время: например, миграции БД

5. Сравнение всех scopes

ScopeСозданиеОчисткаUse CaseПримечание
functionПеред каждым тестомПосле каждого тестаБольшинство случаевДефолт, полная изоляция
classПеред классомПосле классаТесты в классеОсторожно с состоянием
moduleПеред модулемПосле модуляОбщие данныеОдин раз на модуль
sessionВ начале сессииВ конце сессииИнфраструктураОдин раз на всю сессию

6. Практические примеры

Пример 1: Database с function scope

@pytest.fixture(scope="function")
def db_session():
    session = create_session()
    yield session
    session.rollback()  # Откатываем транзакцию
    session.close()

def test_create_user(db_session):
    user = db_session.query(User).first()
    assert user is not None

Пример 2: Docker контейнер с session scope

@pytest.fixture(scope="session")
def docker_compose():
    # Поднимаем контейнеры один раз
    os.system("docker-compose up -d")
    yield
    os.system("docker-compose down")  # Опускаем в конце

@pytest.fixture(scope="session", depends_on=["docker_compose"])
def postgres_url():
    return "postgresql://user:pass@localhost:5432/testdb"

Пример 3: Параметризированные fixtures с разными scopes

@pytest.fixture(params=["sqlite", "postgres"], scope="session")
def database_url(request):
    if request.param == "sqlite":
        yield "sqlite:///:memory:"
    else:
        yield "postgresql://localhost/testdb"

def test_connection(database_url):
    assert database_url is not None  # Выполнится дважды

7. Зависимости между fixtures с разными scopes

# Session-scoped fixture
@pytest.fixture(scope="session")
def config():
    return {"debug": True, "timeout": 30}

# Function-scoped fixture зависит от session-scoped
@pytest.fixture(scope="function")
def logger(config):
    return Logger(debug=config["debug"])

def test_logging(logger):
    logger.log("Test message")

8. Autouse fixtures

# Автоматически использует fixture для всех тестов
@pytest.fixture(autouse=True, scope="function")
def reset_database():
    print("\nResetting database...")
    clear_all_tables()
    yield
    print("Cleanup complete")

def test_1():
    pass  # reset_database вызовется автоматически

def test_2():
    pass  # reset_database вызовется автоматически

9. Чек-лист выбора scope

  1. Начни с function (изоляция, безопасность)
  2. Переходи на class/module если есть повторяющиеся дорогие операции
  3. session для инфраструктуры (Docker, БД, API)
  4. Помни о побочных эффектах (состояние между тестами)
  5. Используй conftest.py для общих fixtures
  6. Документируй scope в комментариях

10. conftest.py — общие fixtures

# conftest.py в корне тестов
import pytest
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

@pytest.fixture(scope="session")
def db_engine():
    """Session-scoped: создание engine БД"""
    engine = create_engine("postgresql://localhost/testdb")
    yield engine
    engine.dispose()

@pytest.fixture(scope="function")
def db_session(db_engine):
    """Function-scoped: сессия для каждого теста"""
    connection = db_engine.connect()
    transaction = connection.begin()
    session = sessionmaker(bind=connection)()
    
    yield session
    
    session.close()
    transaction.rollback()
    connection.close()

Выводы

  • function — используй по умолчанию
  • class/module — когда нужна переиспользование
  • session — только для инфраструктуры
  • Больший scope = меньше создания, больше предосторожности
  • Помни о состоянии: larger scopes делают тесты взаимозависимыми
  • conftest.py — для общих fixtures всего проекта
Какие бывают параметры scope в fixture в pytest? | PrepBro