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

Когда применяется декоратор перед каждым тестом или после?

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"
    # После этого теста тоже выполнится очистка

Порядок выполнения:

  1. ПЕРЕД тестом: создание user
  2. Выполнение теста
  3. ПОСЛЕ теста: удаление 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"

Порядок:

  1. ПЕРЕД test_user_name: setup_method
  2. Выполнить test_user_name
  3. ПОСЛЕ test_user_name: teardown_method
  4. ПЕРЕД test_user_email: setup_method (заново!)
  5. Выполнить test_user_email
  6. ПОСЛЕ 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 (сложно отладить)

Порядок выполнения:

  1. ПЕРЕД тестом: выполняется fixture setup
  2. Выполняется сам тест
  3. ПОСЛЕ теста: выполняется fixture teardown (cleanup)
Когда применяется декоратор перед каждым тестом или после? | PrepBro