Что такое скоуп у фикстуры?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Скоуп у фикстуры в pytest
Скоуп (scope) фикстуры — это время жизни объекта, который создаёт фикстура. Он определяет, когда фикстура инициализируется и когда очищается (teardown).
Пять уровней скоупа
1. function (По умолчанию)
Фикстура создаётся заново для каждого теста.
import pytest
@pytest.fixture # scope='function' по умолчанию
def user():
print("Creating user")
user = {"id": 1, "name": "John"}
yield user
print("Cleaning up user")
def test_user_first(user):
assert user["name"] == "John"
# Creating user
# Cleaning up user
def test_user_second(user):
assert user["id"] == 1
# Creating user (новый объект!)
# Cleaning up user
# Вывод:
# Creating user
# Cleaning up user
# Creating user
# Cleaning up user
Когда использовать: При тестировании функций, которые модифицируют объект.
2. class
Фикстура создаётся один раз на класс тестов.
@pytest.fixture(scope='class')
def database():
print("Connecting to DB")
db = connect_to_db()
yield db
print("Closing DB")
class TestUsers:
def test_create_user(self, database):
database.insert("INSERT INTO users...")
# Connecting to DB
def test_get_user(self, database):
result = database.query("SELECT * FROM users")
# Используется ОДНО и то же подключение
def test_delete_user(self, database):
database.delete("DELETE FROM users...")
# Closing DB (после всех тестов класса)
# Вывод:
# Connecting to DB
# Closing DB
Когда использовать: При работе с дорогостоящим ресурсом (БД, API).
3. module
Фикстура создаётся один раз на файл тестов.
# test_auth.py
@pytest.fixture(scope='module')
def app():
print("Starting Flask app")
app = create_app()
app.config['TESTING'] = True
yield app
print("Shutting down app")
def test_login(app):
with app.test_client() as client:
response = client.post('/login', json={"user": "admin"})
# Starting Flask app
def test_logout(app):
with app.test_client() as client:
response = client.post('/logout')
# Используется ТОТ ЖЕ экземпляр app
# Shutting down app (после всех тестов в файле)
# Вывод:
# Starting Flask app
# Shutting down app
Когда использовать: При инициализации тестового окружения один раз.
4. session
Фикстура создаётся один раз на сессию тестирования (весь тестовый прогон).
@pytest.fixture(scope='session')
def redis_client():
print("Connecting to Redis")
client = redis.Redis()
yield client
print("Closing Redis")
# Если запустить `pytest test_*.py`
# Redis подключится один раз для ВСЕХ тестов
# test_file1.py::test_1 (использует redis_client)
# test_file1.py::test_2 (используют ОДНО подключение)
# test_file2.py::test_3 (используют ОДНО подключение)
# ... затем очистка
Когда использовать: Для инициализации единого ресурса для всех тестов.
5. package
Фикстура создаётся один раз на пакет.
# conftest.py в директории tests/
@pytest.fixture(scope='package')
def config():
print("Loading config")
config = load_config()
yield config
print("Cleaning config")
# tests/unit/test_models.py
def test_model(config):
assert config['db_host'] == 'localhost'
# tests/integration/test_api.py
def test_api(config):
assert config['api_key'] is not None
# Один раз для package tests/, затем для package tests/unit/, затем tests/integration/
Практический пример: БД с разными скоупами
import pytest
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
# Скоуп session: одно подключение для всех тестов
@pytest.fixture(scope='session')
def db_engine():
print("Creating DB engine")
engine = create_engine('sqlite:///:memory:')
yield engine
print("Disposing engine")
# Скоуп function: новая сессия для каждого теста
@pytest.fixture(scope='function')
def db_session(db_engine):
print("Creating session")
connection = db_engine.connect()
transaction = connection.begin()
Session = sessionmaker(bind=connection)
session = Session()
yield session
session.close()
transaction.rollback() # Откатываем после теста
connection.close()
print("Closing session")
class TestUserRepository:
def test_create_user(self, db_session):
# Creating session (новая транзакция)
user = User(name="John")
db_session.add(user)
db_session.commit()
assert user.id is not None
# Closing session (откатываем)
def test_get_user(self, db_session):
# Creating session (новая транзакция)
# Closing session
# Вывод:
# Creating DB engine
# Creating session
# Closing session (rollback)
# Creating session
# Closing session (rollback)
# Disposing engine
Зависимости между фикстурами
@pytest.fixture(scope='session')
def app():
return Flask(__name__)
@pytest.fixture(scope='function')
def client(app): # Зависит от app
return app.test_client()
def test_request(client): # Используем client
response = client.get('/')
assert response.status_code == 200
# app создаётся один раз
# client создаётся для каждого теста
Автоиспользование фикстур
@pytest.fixture(scope='function', autouse=True)
def clear_cache():
"""Очищает кэш перед каждым тестом автоматически"""
print("Clearing cache")
cache.clear()
yield
print("Cache cleared")
def test_any():
# clear_cache вызовется АВТОМАТИЧЕСКИ
pass
Таблица скоупов
| Скоуп | Частота создания | Использование |
|---|---|---|
| function | Для каждого теста | Изолированные тесты |
| class | Один раз на класс | Группы тестов |
| module | Один раз на файл | Инициализация |
| session | Один раз на сессию | Дорогие ресурсы |
| package | Один раз на пакет | Конфиг |
Важные заметки
- Меньший скоуп вложен в больший (function → class → module → session)
- Фикстура с большим скоупом может использовать фикстуру с меньшим скоупом
- Обратное НЕВОЗМОЖНО (function-фикстура не может использовать session-фикстуру)
- Скоуп влияет на производительность тестов
Заключение
Правильный выбор скоупа — это баланс между скоростью (большие скоупы быстрее) и изолированностью (маленькие скоупы чище). Лучше начинать с function и увеличивать скоуп, только если нужна оптимизация.