Как объявляется декоратор в контексте тестирования?

«Как объявляется декоратор в контексте тестирования?» — вопрос из категории Python, который задают на 24% собеседований AQA / Automation. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

В контексте тестирования на Python декораторы широко используются в фреймворках (pytest, unittest) для пометки тестов, настройки фикстур, параметризации и управления их выполнением. Объявление кастомного декоратора для тестов следует общему синтаксису Python.

1. Базовое объявление декоратора для логирования или измерения времени выполнения теста:

import time
import functools

def log_execution_time(func):
    """Декоратор для логирования времени выполнения теста."""
    @functools.wraps(func)  # Сохраняем метаданные оригинальной функции
    def wrapper(*args, **kwargs):
        start_time = time.perf_counter()
        result = func(*args, **kwargs)  # Выполняем сам тест
        end_time = time.perf_counter()
        print(f"Тест {func.__name__} выполнен за {end_time - start_time:.4f} сек")
        return result
    return wrapper

# Применение к тестовой функции
@log_execution_time
def test_calculation_performance():
    # ... код теста ...
    assert heavy_calculation() == expected_result

2. Декоратор для повторения падающего теста (retry logic):

def retry_on_failure(max_attempts=3):
    """Декоратор для повторного запуска теста при неудаче."""
    def decorator(test_func):
        @functools.wraps(test_func)
        def wrapper(*args, **kwargs):
            for attempt in range(1, max_attempts + 1):
                try:
                    return test_func(*args, **kwargs)
                except AssertionError as e:
                    if attempt == max_attempts:
                        raise  # Пробрасываем ошибку после последней попытки
                    print(f"Попытка {attempt} не удалась, повторяем... ({e})")
            return None
        return wrapper
    return decorator

@retry_on_failure(max_attempts=2)
def test_flaky_integration():
    # Этот тест иногда падает из-за внешней зависимости
    response = call_unstable_api()
    assert response.status_code == 200

3. Использование встроенных декораторов фреймворков (pytest):

import pytest

@pytest.mark.slow  # Помечаем тест как медленный
@pytest.mark.parametrize("input,expected", [("1+2", 3), ("2*3", 6)])  # Параметризация
@log_execution_time  # Можно комбинировать декораторы
def test_parametrized_calculation(input, expected):
    assert eval(input) == expected

Зачем это QA-инженеру: Кастомные декораторы помогают добавлять сквозную функциональность (логирование, retry, setup/teardown) к множеству тестов без дублирования кода, делая тестовый набор более управляемым и информативным.