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

Реализовывал ли собственный декоратор

2.0 Middle🔥 162 комментариев
#Теория тестирования

Комментарии (2)

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Да, многократно реализовывал собственные декораторы

За годы работы с Python в контексте QA Automation, создание собственных декораторов стало рутинной, но мощной практикой для решения широкого круга задач: от улучшения стабильности тестов и логирования до управления временем выполнения и интеграции с системами отчетности. Декораторы — это элегантный инструмент метапрограммирования, позволяющий инкапсулировать сквозную функциональность (cross-cutting concerns) без модификации исходного кода тестовых методов.

Практические примеры из опыта автоматизации

1. Декоратор для логирования и трассировки выполнения

Этот декоратор фиксирует начало, конец, аргументы и результат вызова функции, что незаменимо при отладке сложных тестовых сценариев или анализе падений.

import functools
import logging

def log_execution(func):
    """
    Декоратор для логирования входа, выхода и ошибок функции.
    """
    @functools.wraps(func)  # Сохраняем метаданные оригинальной функции
    def wrapper(*args, **kwargs):
        logger = logging.getLogger(func.__module__)
        # Логируем начало выполнения
        logger.info(f"Выполнение {func.__name__} с args={args}, kwargs={kwargs}")
        try:
            result = func(*args, **kwargs)
            # Логируем успешное завершение
            logger.info(f"Функция {func.__name__} завершилась успешно. Результат: {result}")
            return result
        except Exception as e:
            # Логируем исключение с полным traceback
            logger.exception(f"Функция {func.__name__} завершилась с ошибкой: {e}")
            raise  # Пробрасываем исключение дальше
    return wrapper

# Использование в тестовом классе
class TestUserRegistration:

    @log_execution
    def test_create_user(self, username, email):
        # ... логика теста ...
        return user_id

2. Декоратор для повторных попыток (Retry) для нестабильных операций

Крайне полезен в UI-автоматизации (Selenium) или при работе с нестабильными API/сетевыми вызовами, где возможны случайные таймауты или временные ошибки.

import time
import functools

def retry_on_failure(max_attempts=3, delay=1, exceptions=(Exception,)):
    """
    Повторяет выполнение функции при возникновении указанных исключений.
    """
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            last_exception = None
            for attempt in range(1, max_attempts + 1):
                try:
                    return func(*args, **kwargs)
                except exceptions as e:
                    last_exception = e
                    if attempt < max_attempts:
                        time.sleep(delay * attempt)  # Прогрессивная задержка
                        print(f"Попытка {attempt} для {func.__name__} не удалась. Повтор...")
            # Если все попытки исчерпаны
            raise Exception(f"Функция {func.__name__} не выполнилась после {max_attempts} попыток.") from last_exception
        return wrapper
    return decorator

# Использование для нестабильного API-запроса
class APITests:

    @retry_on_failure(max_attempts=5, delay=2, exceptions=(ConnectionError, TimeoutError))
    def get_unstable_api_data(self, url):
        response = requests.get(url, timeout=5)
        response.raise_for_status()
        return response.json()

3. Декоратор для измерения времени выполнения (Performance Check)

Позволяет выявлять деградацию производительности тестов или проверять выполнение операций в рамках SLA.

import time
import functools

def measure_time(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start_time = time.perf_counter()
        result = func(*args, **kwargs)
        end_time = time.perf_counter()
        execution_time = end_time - start_time
        print(f"[ТАЙМИНГ] Функция {func.__name__} выполнилась за {execution_time:.4f} секунд")
        # Также можно логировать в систему мониторинга (например, InfluxDB + Grafana)
        return result
    return wrapper

# Использование для критичных по времени операций
class DataBaseTests:

    @measure_time
    def test_bulk_insert_performance(self, data_set):
        # ... массовая вставка данных ...
        pass

4. Декоратор для условного пропуска тестов (Conditional Skip) в зависимости от конфигурации

Позволяет динамически управлять набором выполняемых тестов на основе флагов окружения, версии приложения или доступности внешних сервисов.

import os
import functools

def skip_if(condition, reason):
    """
    Пропускает тест, если условие condition истинно.
    """
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            if condition:
                print(f"Тест {func.__name__} пропущен. Причина: {reason}")
                return None  # Или можно использовать raise unittest.SkipTest(reason)
            return func(*args, **kwargs)
        return wrapper
    return decorator

# Использование
IS_PRODUCTION = os.getenv("ENV") == "prod"

class SmokeTests:

    @skip_if(condition=IS_PRODUCTION, reason="Деструктивный тест не должен выполняться на Prod")
    def test_delete_user(self):
        # ... логика теста ...
        pass

Ключевые принципы, которые я учитываю при создании декораторов

  • Сохранение метаданных: Всегда использую @functools.wraps(func) для корректной работы интроспекции (имя функции, документация). Это критично для корректного отображения имен тестов в Allure, pytest или unittest отчетах.
  • Гибкость и параметризация: Часто реализую декораторы через фабрику декораторов (функцию, возвращающую декоратор, как в retry_on_failure), чтобы можно было передавать параметры (max_attempts, delay).
  • Обработка исключений: В декораторах для тестов важно аккуратно обрабатывать исключения: логировать, но зачастую пробрасывать их дальше, чтобы test runner корректно интерпретировал результат теста как FAIL, а не как ошибку в декораторе.
  • Сочетаемость: Декораторы должны корректно работать в стеке (когда на функцию навешано несколько декораторов). Порядок применения может влиять на поведение.
  • Утилитарность и переиспользование: Лучшие декораторы абстрагированы от конкретной бизнес-логики и вынесены в общие модули (utils/decorators.py), откуда их можно импортировать в любом проекте.

Таким образом, написание декораторов — это не просто академическое упражнение, а практический навык, который напрямую влияет на надежность, поддерживаемость и информативность автоматизированных тестов. Они позволяют создавать чистый, DRY (Don't Repeat Yourself) код, где сама тестовая логика сосредоточена на проверке требований, а вспомогательная функциональность инкапсулирована в декораторы.

Реализовывал ли собственный декоратор | PrepBro