← Назад к вопросам
Когда применяется декоратор перед каждым тестом или после?
2.0 Middle🔥 141 комментариев
#Python Core#Тестирование
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Декораторы перед и после тестов в pytest
Этот вопрос обычно касается фиксчур (fixtures) и setup/teardown методов в тестировании. Давайте разберемся в разных подходах к управлению состоянием тестов.
1. pytest Fixtures с scope
Fixtures — это встроенный механизм для подготовки данных перед тестом и очистки после теста.
Fixture с function scope (по умолчанию)
import pytest
from myapp.models import User
@pytest.fixture
def user():
# Выполняется ПЕРЕД каждым тестом
user = User.objects.create(name="John", email="john@example.com")
yield user
# Выполняется ПОСЛЕ каждого теста (cleanup)
user.delete()
def test_user_creation(user):
assert user.name == "John"
# После этого теста выполнится очистка
def test_user_email(user):
assert user.email == "john@example.com"
# После этого теста тоже выполнится очистка
Порядок выполнения:
- ПЕРЕД тестом: создание user
- Выполнение теста
- ПОСЛЕ теста: удаление user
Fixture с module scope
@pytest.fixture(scope="module")
def test_database():
# Выполняется один раз ПЕРЕД всеми тестами в модуле
db = setup_database()
yield db
# Выполняется один раз ПОСЛЕ всех тестов в модуле
cleanup_database(db)
def test_query_1(test_database):
result = test_database.query("SELECT * FROM users")
assert len(result) > 0
def test_query_2(test_database):
result = test_database.query("SELECT * FROM posts")
assert len(result) > 0
2. Встроенные fixtures: setup_method и teardown_method
Это старый подход (unittest стиль), но все еще используется:
import pytest
class TestUser:
def setup_method(self):
# Выполняется ПЕРЕД каждым методом теста
self.user = User.objects.create(name="John")
def teardown_method(self):
# Выполняется ПОСЛЕ каждого методе теста
self.user.delete()
def test_user_name(self):
assert self.user.name == "John"
def test_user_email(self):
self.user.email = "new@example.com"
assert self.user.email == "new@example.com"
Порядок:
- ПЕРЕД test_user_name: setup_method
- Выполнить test_user_name
- ПОСЛЕ test_user_name: teardown_method
- ПЕРЕД test_user_email: setup_method (заново!)
- Выполнить test_user_email
- ПОСЛЕ test_user_email: teardown_method
3. Декораторы для тестов
Декоратор перед тестом (@pytest.mark.parametrize)
import pytest
@pytest.mark.parametrize("username,email", [
("john", "john@example.com"),
("jane", "jane@example.com"),
("bob", "bob@example.com"),
])
def test_user_creation(username, email):
user = User.objects.create(name=username, email=email)
assert user.name == username
assert user.email == email
user.delete()
Этот декоратор применяется ПЕРЕД тестом, генерируя несколько тестов с разными параметрами.
Декоратор для пропуска теста (@pytest.mark.skip)
@pytest.mark.skip(reason="Not implemented yet")
def test_future_feature():
pass
# Или условно
@pytest.mark.skipif(not HAS_REDIS, reason="Redis not available")
def test_with_redis():
redis_client.set("key", "value")
Декоратор для ожидаемых ошибок (@pytest.mark.xfail)
@pytest.mark.xfail(reason="Bug in feature X")
def test_broken_feature():
# Этот тест ожидаемо падает
assert 1 == 2 # Это ОК
4. Декоратор перед и после функции (не для тестов)
Если нужен декоратор для обычной функции:
def my_decorator(func):
def wrapper(*args, **kwargs):
# Выполняется ПЕРЕД функцией
print("Before function")
result = func(*args, **kwargs)
# Выполняется ПОСЛЕ функции
print("After function")
return result
return wrapper
@my_decorator
def add(a, b):
print(f"Executing: {a} + {b}")
return a + b
add(2, 3)
# Вывод:
# Before function
# Executing: 2 + 3
# After function
Применение декоратора для тестов
def setup_teardown(func):
def wrapper():
# ПЕРЕД тестом
print("Setup")
result = func()
# ПОСЛЕ теста
print("Teardown")
return result
return wrapper
@setup_teardown
def test_something():
assert 1 == 1
test_something()
# Вывод:
# Setup
# Teardown
Но это плохая практика! Используйте fixtures вместо этого.
5. Context Manager (with statement)
Альтернатива fixtures для управления ресурсами:
from contextlib import contextmanager
@contextmanager
def user_context():
# ПЕРЕД
user = User.objects.create(name="John")
try:
yield user
finally:
# ПОСЛЕ (даже если произойдет исключение)
user.delete()
def test_with_context():
with user_context() as user:
# Пользователь создан
assert user.name == "John"
# Пользователь удален
6. Практический пример: сложная подготовка
import pytest
from django.test import TransactionTestCase
@pytest.fixture(scope="function")
def setup_complete_user(db):
# ПЕРЕД: создание пользователя с полными данными
user = User.objects.create(name="John", email="john@example.com")
profile = Profile.objects.create(user=user, bio="Test user")
posts = [
Post.objects.create(user=user, title=f"Post {i}")
for i in range(5)
]
yield {
"user": user,
"profile": profile,
"posts": posts
}
# ПОСЛЕ: полная очистка
user.delete()
# Cascade удаления позаботится об остальном
def test_user_has_posts(setup_complete_user):
data = setup_complete_user
posts = Post.objects.filter(user=data["user"])
assert posts.count() == 5
def test_user_profile(setup_complete_user):
data = setup_complete_user
assert data["profile"].user == data["user"]
7. Использование mocking для избежания лишних операций
from unittest.mock import patch
@patch('myapp.models.User.objects.create')
def test_with_mock(mock_create):
# Перед: mock готов
mock_create.return_value = User(name="Mocked")
result = User.objects.create(name="Test")
# Проверка
assert result.name == "Mocked"
mock_create.assert_called_once()
# После: mock автоматически очищается
8. Порядок выполнения в pytest
@pytest.fixture(scope="module")
def setup_module():
print("1. Module setup")
yield
print("6. Module teardown")
@pytest.fixture
def setup_function():
print("2. Function setup")
yield
print("5. Function teardown")
def test_example(setup_function):
print("3. Test execution")
assert True
print("4. Test end")
# Вывод:
# 1. Module setup
# 2. Function setup
# 3. Test execution
# 4. Test end
# 5. Function teardown
# 6. Module teardown
Рекомендации
Используйте:
- pytest fixtures — современный стандарт для setup/teardown
- @pytest.mark.parametrize — для параметризации тестов
- scope="function" — для изоляции тестов (по умолчанию)
- scope="module" или "session" — для дорогостоящих ресурсов (БД, API)
- Context managers — для управления ресурсами в тестах
Избегайте:
- Декораторов, которые делают setup/teardown (используйте fixtures)
- Общего состояния между тестами (изолируйте каждый тест)
- Очень глубокой вложенности fixtures (сложно отладить)
Порядок выполнения:
- ПЕРЕД тестом: выполняется fixture setup
- Выполняется сам тест
- ПОСЛЕ теста: выполняется fixture teardown (cleanup)