FlakyDetector 2.0: Как я превратил сырое исследование в продакшен-инструмент с AST, ML и красивым дашбордом…. AST.. AST. CatBoost.. AST. CatBoost. fastapi.. AST. CatBoost. fastapi. flaky tests.. AST. CatBoost. fastapi. flaky tests. machine learning.. AST. CatBoost. fastapi. flaky tests. machine learning. python.. AST. CatBoost. fastapi. flaky tests. machine learning. python. static analysis.. AST. CatBoost. fastapi. flaky tests. machine learning. python. static analysis. test smells.. AST. CatBoost. fastapi. flaky tests. machine learning. python. static analysis. test smells. тестирование.

— История о том, почему половина ваших тестов падает «сама по себе», и как я научился находить виновников по исходному коду.

 Лид: Час на перезапуск билда — это норма?

Представь: пятница, вечер. Ты запускаешь CI для последнего пулл-реквеста, идёшь наливать кофе, возвращаешься… а билд упал. Один тест. Ты перезапускаешь проходит. «Флаки», — вздыхаешь ты и ставишь лейбл flaky. На следующей неделе история повторяется. Потом ещё раз.

Мы привыкли, что нестабильные тесты — это неизбежное зло. Их ловят повторными прогонами, а если повезёт вырезают. Но знаешь, что реально бесит? В 80% случаев корень проблемы можно найти, просто посмотрев на код теста.

Я написал инструмент, который это делает автоматически. Без логов CI, без истории прогонов — только AST и машинное обучение.

Назвал его FlakyDetector. Первая версия была исследовательским прототипом (про него у меня выходила статья на Хабре). А теперь — это полноценный продукт: CLI, веб-дашборд, CI-интеграция и даже React-фронтенд. И да, он open source.

Давай разберёмся, как это устроено.

Контекст: Что такое flaky test и почему их не лечат логгерами?

Flaky-тест — это тест, который без изменения кода может и упасть, и пройти. Классика:

  • time.sleep(0.5) — а на медленной машине нужно 0.7.

  • datetime.now() — а завтра упадёт из-за перехода на летнее время.

  • Глобальная переменная counter, которую мутируют тесты параллельно.

  • Запрос к реальному API без мока то доступ есть, то нет.

Большинство проектов реагируют просто: перезапускают билд. Некоторые используют pytest-rerunfailures. Но это лечение симптомов, а не причины.

Можно, конечно, написать линтер, который ищет time.sleep. Но что делать с «запахом» цикломатической сложности в тесте? Или с тем, что тест одновременно зависит от времени и от глобального состояния? Простые правила не работают.

Нужно что-то умнее. Например, статический анализ + ML.

Архитектура.
Архитектура.

Суть простыми словами: Анализатор, который видит грядущие проблемы

FlakyDetector читает твой тест как программист, но без усталости. Он:

  1. Парсит код в абстрактное синтаксическое дерево (AST) — ту самую структуру, которую Python строит перед компиляцией.

  2. Находит антипаттерны — time.sleep, вызовы datetime.now(), модификацию глобальных переменных, незамокированные HTTP-запросы и ещё 8 видов.

  3. Собирает метрики — количество таких вызовов, цикломатическую сложность, соотношение асинхронных операций к логам.

  4. Кормит всё это в обученную модель CatBoost, которая выдаёт вероятность, что тест флаки.


AST-паттерны: больше чем просто time.sleep

Я добавил детекцию 11 видов антипаттернов, разбитых на категории:

  • Timing – sleeptime.timedatetime.nowthreading.Timer.

  • State – глобальные переменные, модификация окружения (os.environ), random.

  • Network – вызовы requests.gethttpx без мока.

  • Concurrency – threadingasyncio.create_task без ожидания.

Каждый найденный паттерн получает severity (LOW/MEDIUM/CRITICAL) и confidence (насколько уверен детектор, что это именно проблема).


Feature Engineering: от AST к 37 числам

Просто списка паттернов мало. Модели нужно количество и контекст. Я сформировал 37-мерный вектор:

  • 16 счётчиков конкретных AST-узлов.

  • 9 агрегированных баллов по категориям (Timing, State, Network).

  • Цикломатическая сложность тестовой функции (если >10 — запах).

  • Производные признаки: ast_to_log_ratio (много операций, мало логов — подозрительно), pattern_diversity.

  • 8 признаков уверенности: максимальная, средняя, разброс.


Модель: CatBoost вместо чёрного ящика

Я выбрал CatBoost, потому что он:

  • Работает с категориальными признаками (у нас они есть).

  • Даёт Feature Importance — можно объяснить, почему тест признан флаки.

  • Легко сериализуется в .cbm и грузится в прод.

Модель обучена на синтетическом датасете (смесь реальных проектов из открытых репозиториев + сгенерированные флаки-паттерны). Точность на валидации — 87% (при полноте 82%). Не топ, но для первой версии — достойно. А главное — объяснимо.


Цифры, бенчмарки и сравнение

Чтобы ты понимал масштаб:

Метрика

Значение

AST-паттернов

11+

Размерность вектора

37

Модель

CatBoost (gradient boosting)

Время анализа одного теста

~50 мс (на Core i5)

Языки

Python 3.12+

Интерфейсы

CLI, REST API, React Dashboard

Интеграции

GitHub Actions, Docker, pre-commit

Конкуренты?
Есть плагины к flake8 (flake8-flaky-tests), но они ищут только time.sleep и pytest.mark.flaky. Есть pytest-repeat, но он не анализирует код. А ML-подхода в open source я не видел вообще. FlakyDetector — уникален.

Практическое применение: Как запустить уже сегодня

Два варианта — для быстрых и для основательных.

CLI-сканер (быстро)

git clone https://github.com/Artem7898/flakydetector
cd flakydetector
uv venv --python 3.12
source .venv/bin/activate
uv pip install -e ".[dev]"
python scripts/train_model.py  # сгенерировать датасет и обучить модель
uv run python scripts/scan_folder.py ./my_project/tests/

Вывод — красивая таблица в терминале (спасибо rich):

📂 Scanning: ./my_project/tests/ ...

                               Flaky Patterns Detected
┏━━━━━━━━━━━━━━━━┳━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━━┓
┃ File           ┃ Line  ┃ Pattern       ┃ Severity ┃ Confidence┃
┡━━━━━━━━━━━━━━━━╇━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━┩
│ test_api.py    │    42 │ time_sleep    │ MEDIUM   │       90% │
│ test_db.py     │    15 │ global_mutate │ CRITICAL │       95% │
└────────────────┴───────┴───────────────┴──────────┴───────────┘

Docker + веб-дашборд (для менеджеров)

docker-compose up --build
# бэкенд на http://localhost:8001
cd dashboard_frontend && npm install && npm run dev
# фронт на http://localhost:3000

Открываешь — а там графики распределения severity, список файлов с подсветкой синтаксиса, можно кликнуть на паттерн — увидишь строку кода.

CI/CD: блокируем PR автоматически

Добавь в .github/workflows/flaky_detection.yml:

- name: Run FlakyDetector
  run: uv run python scripts/scan_folder.py ./tests --fail-on-critical

Если ты дочитал до сюда — спасибо. Ты уже на голову выше тех, кто просто перезапускает билд три раза подряд.

FlakyDetector — это мой эксперимент на стыке статического анализа и ML. Он не идеален, но он уже экономит время. Попробуй просканировать свои тесты. Уверен, найдёшь пару сюрпризов.

Репозиторий: github.com/Artem7898/flakydetector
Предыдущая статья (первая версия): habr.com/ru/articles/969354

Автор: Artem7898

Источник