← Назад к вопросам
Какие используешь 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, который удовлетворяет тест требованиям:
- function — по умолчанию (изоляция > скорость)
- session — для дорогостоящих ресурсов (БД, Redis)
- module — для конфигурации
- class — редко (предпочитаю function)
- package — очень редко
Основной принцип: каждый тест должен быть независим и воспроизводим.