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

Какие используешь scope у fixture в тестировании?

1.8 Middle🔥 281 комментариев
#Тестирование

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

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

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

Scope у fixture в pytest

Scope определяет когда и как часто выполняется fixture. Это критично для производительности тестов и управления ресурсами. Я использую разные scope в зависимости от задачи.

Основные scope'ы

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

Fixture выполняется для каждого теста:

import pytest

@pytest.fixture(scope="function")
def database():
    """Fixture выполняется перед каждым тестом"""
    print("\n[SETUP] Подключение к БД")
    db = connect_to_database()
    yield db
    print("[TEARDOWN] Закрытие БД")
    db.close()

def test_user_creation(database):
    # database создаётся ДО этого теста
    user = database.create_user("John")
    assert user.name == "John"
    # database закрывается ПОСЛЕ этого теста

def test_user_deletion(database):
    # database создаётся ДО этого теста (новое подключение!)
    user = database.create_user("Jane")
    database.delete_user(user.id)
    assert not database.user_exists(user.id)
    # database закрывается ПОСЛЕ этого теста

Вывод:

test_user_creation.py::
[SETUP] Подключение к БД
[TEARDOWN] Закрытие БД
PASSED

test_user_deletion.py::
[SETUP] Подключение к БД
[TEARDOWN] Закрытие БД
PASSED

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

  • Изоляция тестов (тесты не влияют друг на друга)
  • Небольшие ресурсы (быстрое создание/удаление)
  • Нужна чистая state для каждого теста

2. class (для классов тестов)

Fixture выполняется один раз для класса тестов:

class TestUserService:
    @pytest.fixture(scope="class", autouse=True)
    def setup_service(self):
        """Выполняется один раз в начале класса"""
        print("\n[CLASS SETUP]")
        self.service = UserService()
        yield
        print("[CLASS TEARDOWN]")
    
    def test_create_user(self):
        user = self.service.create_user("Alice")
        assert user is not None
    
    def test_get_user(self):
        # Используется тот же service, что и в предыдущем тесте
        users = self.service.get_all_users()
        assert len(users) > 0
    
    def test_delete_user(self):
        # Все пользователи из предыдущих тестов ещё здесь!
        users = self.service.get_all_users()
        for user in users:
            self.service.delete_user(user.id)

Вывод:

[CLASS SETUP]
test_create_user PASSED
test_get_user PASSED
test_delete_user PASSED
[CLASS TEARDOWN]

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

  • Тесты в одном классе зависят друг от друга
  • Дорогостоящее инициализирование ресурсов
  • Не критична полная изоляция

3. module (для модуля)

Fixture выполняется один раз для всего модуля тестов:

# conftest.py или test_module.py
@pytest.fixture(scope="module")
def api_client():
    """Выполняется один раз для всех тестов в модуле"""
    print("\n[MODULE SETUP] Инициализация API клиента")
    client = APIClient(base_url="https://api.example.com")
    yield client
    print("[MODULE TEARDOWN] Закрытие API клиента")

# test_api.py
def test_get_users(api_client):
    users = api_client.get_users()
    assert len(users) > 0

def test_get_user_by_id(api_client):
    # Используется тот же api_client
    user = api_client.get_user(1)
    assert user.id == 1

def test_create_user(api_client):
    # Тот же api_client из первого теста
    new_user = api_client.create_user({"name": "Bob"})
    assert new_user.id is not None

Вывод:

[MODULE SETUP] Инициализация API клиента
test_get_users PASSED
test_get_user_by_id PASSED
test_create_user PASSED
[MODULE TEARDOWN] Закрытие API клиента

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

  • Инициализация контекста (конфиг, логирование)
  • Тесты в одном модуле используют одни ресурсы
  • Нужна производительность

4. session (для всей сессии тестов)

Fixture выполняется один раз для всех тестов в проекте:

# conftest.py
import pytest

@pytest.fixture(scope="session")
def database_url():
    """Выполняется один раз для всей сессии тестов"""
    print("\n[SESSION SETUP] Подключение к тестовой БД")
    url = "postgresql://test:test@localhost/test_db"
    yield url
    print("[SESSION TEARDOWN] Очистка тестовой БД")

@pytest.fixture(scope="session")
def redis_client():
    """Кэш для всех тестов"""
    print("\n[SESSION SETUP] Подключение к Redis")
    client = redis.Redis(host='localhost', port=6379)
    client.flushdb()
    yield client
    print("[SESSION TEARDOWN] Отключение Redis")

# test_users.py
def test_user_creation(database_url):
    # Используется одно подключение для всех тестов
    db = connect(database_url)
    assert db is not None

# test_products.py
def test_product_creation(database_url, redis_client):
    # Тот же database_url и redis_client что и в других тестах
    pass

Вывод:

[SESSION SETUP] Подключение к тестовой БД
[SESSION SETUP] Подключение к Redis

test_users.py::test_user_creation PASSED
test_products.py::test_product_creation PASSED

[SESSION TEARDOWN] Отключение Redis
[SESSION TEARDOWN] Очистка тестовой БД

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

  • Очень дорогостоящие ресурсы (база данных, Docker контейнеры)
  • Конфигурация, которая не меняется между тестами
  • Максимальная производительность

5. package (для пакета тестов)

Fixture выполняется один раз для каждого пакета:

# tests/unit/conftest.py
@pytest.fixture(scope="package")
def unit_setup():
    print("\n[PACKAGE SETUP]")
    yield
    print("[PACKAGE TEARDOWN]")

# tests/integration/conftest.py
@pytest.fixture(scope="package")
def integration_setup():
    print("\n[INTEGRATION SETUP]")
    yield
    print("[INTEGRATION TEARDOWN]")

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

  • Разделение настроек для разных типов тестов
  • Unit и integration тесты нуждаются в разных конфигурациях

Практический пример: Полная стратегия

# conftest.py - session scope
@pytest.fixture(scope="session")
def config():
    """Конфигурация для всех тестов"""
    return {
        'db_url': 'postgresql://test:test@localhost/test_db',
        'api_url': 'https://api.test.local',
        'timeout': 30
    }

@pytest.fixture(scope="session")
def db_connection(config):
    """Подключение к БД (дорогостоящее)"""
    conn = connect_db(config['db_url'])
    yield conn
    conn.close()

# conftest.py - function scope
@pytest.fixture(scope="function")
def clean_database(db_connection):
    """Очищаем БД перед каждым тестом"""
    db_connection.clear_all_tables()
    yield db_connection

@pytest.fixture(scope="function")
def user_service(clean_database):
    """Новый сервис для каждого теста"""
    return UserService(clean_database)

# test_users.py
def test_create_user(user_service):
    user = user_service.create("Alice", "alice@example.com")
    assert user.email == "alice@example.com"

def test_get_user(user_service):
    # Новый user_service, новая БД!
    user_service.create("Bob", "bob@example.com")
    users = user_service.get_all()
    assert len(users) == 1  # Только Bob

Сравнение scope'ов

ScopeКогда вызываетсяИспользованиеСкоростьИзоляция
functionПеред каждым тестомCRUD операцииМедленнаяИдеальная
classОдин раз для классаКлассовые тестыСредняяХорошая
moduleОдин раз для модуляМодульные ресурсыБыстраяСредняя
sessionОдин раз для проектаБД, внешние сервисыОчень быстраяПлохая
packageОдин раз для пакетаРазные типы тестовБыстраяСредняя

Мой выбор scope'а

Правило: Используй самый узкий scope, который удовлетворяет тест требованиям:

  1. function — по умолчанию (изоляция > скорость)
  2. session — для дорогостоящих ресурсов (БД, Redis)
  3. module — для конфигурации
  4. class — редко (предпочитаю function)
  5. package — очень редко

Основной принцип: каждый тест должен быть независим и воспроизводим.

Какие используешь scope у fixture в тестировании? | PrepBro