← Назад к вопросам
Какие бывают параметры 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
- Начни с function (изоляция, безопасность)
- Переходи на class/module если есть повторяющиеся дорогие операции
- session для инфраструктуры (Docker, БД, API)
- Помни о побочных эффектах (состояние между тестами)
- Используй conftest.py для общих fixtures
- Документируй 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 всего проекта