Глубокое погружение в pytest-testmon: Умный отбор тестов для Python-проектов

Глубокое погружение в pytest-testmon: Умный отбор тестов для Python-проектов


Глубокое погружение в pytest-testmon: Умный отбор тестов для Python-проектов


Что такое pytest-testmon и зачем он нужен?

pytest-testmon — это интеллектуальный плагин для pytest, который автоматически определяет, какие тесты нужно запустить после изменений кода. Вместо полного прогона всех тестов каждый раз (что может занимать часы в крупных проектах), testmon использует анализ зависимостей кода, чтобы выполнить только релевантные тесты, экономя до 90% времени.

Проблема, которую решает testmon:
В проектах с 1000+ тестами их полный запуск становится узким местом в разработке. Классические методы вроде pytest -k или ручного указания файлов неэффективны и подвержены ошибкам. Testmon автоматизирует этот процесс через отслеживание связей между кодовой базой и тестами.


Как работает testmon: Магия под капотом

  1. Сбор метрик покрытия:

    • При первом запуске pytest --testmon инструмент выполняет ВСЕ тесты, собирая данные о том, какие строки кода были затронуты каждым тестом.
    • Результаты сохраняются в скрытом файле .testmondata (SQLite-база).
  2. Анализ изменений:

    • При последующих запусках testmon сравнивает текущее состояние кода с предыдущей версией (через контроль версий).
    • Определяет все изменённые файлы, функции и классы.
  3. Интеллектуальный отбор тестов:

    • С помощью собранных данных testmon вычисляет, какие тесты затрагивают изменённые участки кода.
    • Запускаются ТОЛЬКО эти тесты + тесты, помеченные как “ненадёжные” (flaky).
  4. Кеширование и инкрементальность:

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

Ключевые преимущества

  1. Экономия времени:

    # До
    $ pytest  # 15 минут
    
    # После
    $ pytest --testmon  # 47 секунд (если изменён 1 файл)
  2. Автоматизация рутины:

    • Больше не нужно гадать, какие тесты запустить после git pull или правки кода.
  3. Совместимость с CI/CD:

    • Уменьшение времени сборки на 60-80% в пайплайнах.
    • Пример для GitHub Actions:
      steps:
        - name: Run impacted tests
          run: pytest --testmon
  4. Гибкая конфигурация:

    • Исключение файлов через .testmonignore (аналог .gitignore):
      legacy/*
      generated_code.py

Установка и настройка

  1. Установка:

    pip install pytest-testmon
  2. Базовое использование:

    # Первый запуск (собирает данные)
    pytest --testmon
    
    # Последующие запуски (умный отбор)
    pytest --testmon
  3. Интеграция с pytest.ini:

    [pytest]
    addopts = --testmon
  4. Игнорирование файлов: Создайте .testmonignore в корне проекта:

    /migrations/
    /tests/data/*
    *.ipynb

Практические примеры

Сценарий 1: Изменение в бизнес-логике
Допустим, вы исправили функцию calculate_tax() в finance/utils.py. Testmon:

  • Определит все тесты, покрывающие этот файл.
  • Запустит только их, проигнорировав тесты для UI или API.

Сценарий 2: Рефакторинг без изменений поведения
Если правки не затронули исполняемый код (комментарии, форматирование), testmon пропустит запуск тестов.

Сценарий 3: Добавление нового теста
Новый тест будет запущен единожды, а его связи с кодом добавятся в .testmondata.


Ограничения и подводные камни

  1. Динамический импорт:
    Если модули загружаются через importlib или exec, testmon может не отследить зависимости.

  2. Изменение поведения без изменения кода:

    • Внешние API, обновление БД. Решение: ручной запуск с флагом --no-testmon.
  3. Ограниченная поддержка ООП:

    • Наследование и полиморфизм могут требовать дополнительных тестов. Рекомендация: помечать базовые классы как “опасные” через декоратор:
      @pytest.mark.testmon(always_run=True)
      class TestBaseAPI:
          ...
  4. Конфликты с плагинами:

    • Несовместимость с некоторыми плагинами (например, pytest-randomly). Решение: отключать конфликтующие инструменты при использовании testmon.

Интеграция с другими инструментами

  1. pytest-cov (покрытие кода):

    pytest --testmon --cov
    • Coverage-отчёт строится ТОЛЬКО для запущенных тестов. Для полной статистики раз в сутки запускайте полный тестовый прогон.
  2. pytest-xdist (параллельный запуск):

    pytest --testmon -n auto
    • Testmon автоматически распределяет отобранные тесты по потокам.
  3. CI-системы:

    • Кэшируйте .testmondata между билдами:
      # GitHub Actions
      - name: Cache testmon data
        uses: actions/cache@v3
        with:
          path: .testmondata
          key: testmon-${{ hashFiles('**/*.py') }}

Альтернативы и сравнение

ИнструментМетод отбораПлюсыМинусы
pytest-testmonАнализ покрытия кодаВысокая точность, простотаСлепые зоны в динамике
pytest-pickedGit-статус изменённых файловПростая установкаНет связи тест-код
pytest-watchЗапуск при изменении файловРеактивностьНет интеллектуального отбора
unittest —failfastОстановка после первой ошибкиНе требует настройкиЭкономия времени минимальна

Заключение: Когда и как внедрять testmon?

Идеальные кандидаты:

  • Проекты с >200 тестами и временем прогона >5 минут.
  • Команды, практикующие TDD/частыe рефакторинги.
  • CI/CD пайплайны с длительным временем выполнения.

Стратегия внедрения:

  1. Начните с установки в режиме “только сбор данных”:
    pytest --testmon --no-combined
  2. Проведите 2-3 полных прогона для формирования базы.
  3. Перейдите на умный запуск в CI и локально.
  4. Добавьте в .testmonignore генерируемые файлы.
  5. Настройте кеширование данных в CI.

Философский итог: testmon не заменяет тесты, а делает их выполнение осмысленным. Как сказал Кент Бек:

“Тесты — это не роскошь, а инструмент выживания в хаосе разработки. Оптимизируйте их, но не жертвуйте надёжностью”.

# Ваша новая команда для повседневной работы
pytest --testmon -xvv  # Быстро, с детализированным выводом и остановкой при первой ошибке

Официальная документация | Примеры конфигов