- BrainTools - https://www.braintools.ru -
— История о том, почему половина ваших тестов падает «сама по себе», и как я научился находить виновников по исходному коду.
Представь: пятница, вечер. Ты запускаешь CI для последнего пулл-реквеста, идёшь наливать кофе, возвращаешься… а билд упал. Один тест. Ты перезапускаешь проходит. «Флаки», — вздыхаешь ты и ставишь лейбл flaky. На следующей неделе история повторяется. Потом ещё раз.
Мы привыкли, что нестабильные тесты — это неизбежное зло. Их ловят повторными прогонами, а если повезёт вырезают. Но знаешь, что реально бесит? В 80% случаев корень проблемы можно найти, просто посмотрев на код теста.
Я написал инструмент, который это делает автоматически. Без логов CI, без истории прогонов — только AST и машинное обучение [1].
Назвал его FlakyDetector. Первая версия была исследовательским прототипом (про него у меня выходила статья на Хабре [2]). А теперь — это полноценный продукт: CLI, веб-дашборд, CI-интеграция и даже React-фронтенд. И да, он open source.
Давай разберёмся, как это устроено.
Flaky-тест — это тест, который без изменения кода может и упасть, и пройти. Классика:
time.sleep(0.5) — а на медленной машине нужно 0.7.
datetime.now [3]() — а завтра упадёт из-за перехода на летнее время.
Глобальная переменная counter, которую мутируют тесты параллельно.
Запрос к реальному API без мока то доступ есть, то нет.
Большинство проектов реагируют просто: перезапускают билд. Некоторые используют pytest-rerunfailures. Но это лечение симптомов, а не причины.
Можно, конечно, написать линтер, который ищет time.sleep. Но что делать с «запахом» цикломатической сложности в тесте? Или с тем, что тест одновременно зависит от времени и от глобального состояния? Простые правила не работают.
Нужно что-то умнее. Например, статический анализ + ML.
FlakyDetector читает твой тест как программист, но без усталости. Он:
Парсит код в абстрактное синтаксическое дерево (AST) — ту самую структуру, которую Python строит перед компиляцией.
Находит антипаттерны — time.sleep, вызовы datetime.now [3](), модификацию глобальных переменных, незамокированные HTTP-запросы и ещё 8 видов.
Собирает метрики — количество таких вызовов, цикломатическую сложность, соотношение асинхронных операций к логам.
Кормит всё это в обученную модель CatBoost, которая выдаёт вероятность, что тест флаки.
Я добавил детекцию 11 видов антипаттернов, разбитых на категории:
Timing – sleep, time.time, datetime.now [3], threading.Timer.
State – глобальные переменные, модификация окружения (os.environ), random.
Network – вызовы requests.get, httpx без мока.
Concurrency – threading, asyncio.create_task без ожидания.
Каждый найденный паттерн получает severity (LOW/MEDIUM/CRITICAL) и confidence (насколько уверен детектор, что это именно проблема).
Просто списка паттернов мало. Модели нужно количество и контекст. Я сформировал 37-мерный вектор:
16 счётчиков конкретных AST-узлов.
9 агрегированных баллов по категориям (Timing, State, Network).
Цикломатическая сложность тестовой функции (если >10 — запах [4]).
Производные признаки: ast_to_log_ratio (много операций, мало логов — подозрительно), pattern_diversity.
8 признаков уверенности: максимальная, средняя, разброс.
Я выбрал 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 — уникален.
Два варианта — для быстрых и для основательных.
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-compose up --build
# бэкенд на http://localhost:8001
cd dashboard_frontend && npm install && npm run dev
# фронт на http://localhost:3000
Открываешь — а там графики распределения severity, список файлов с подсветкой синтаксиса, можно кликнуть на паттерн — увидишь строку кода.
Добавь в .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 [5]
Предыдущая статья (первая версия): habr.com/ru/articles/969354 [2]
Автор: Artem7898
Источник [6]
Сайт-источник BrainTools: https://www.braintools.ru
Путь до страницы источника: https://www.braintools.ru/article/30317
URLs in this post:
[1] обучение: http://www.braintools.ru/article/5125
[2] статья на Хабре: https://habr.com/ru/articles/969354/
[3] datetime.now: http://datetime.now
[4] запах: http://www.braintools.ru/article/9870
[5] github.com/Artem7898/flakydetector: https://github.com/Artem7898/flakydetector
[6] Источник: https://habr.com/ru/articles/1035782/?utm_campaign=1035782&utm_source=habrahabr&utm_medium=rss
Нажмите здесь для печати.