
Как мы с вашей помощью добавили анализ фикстур, подняли размерность признаков до 42 и научились видеть order dependency до того, как она рушит CI
🔥 Лид: «А давайте просто пометим тест flaky и забудем»
Полгода назад я написал статью про FlakyDetector — инструмент, который ищет нестабильные тесты по одному лишь исходному коду, Потом была статья FlakyDetector 2.0 . AST + CatBoost, 37 признаков, вроде бы всё круто.
Но один комментарий меня добил.
Пользователь Ariless рассказал реальный кейс: в их проекте тест падал с SLOT_OVERLAP — не потому, что в коде теста было что-то плохое, а потому что фикстура была общая на несколько тестов (shared scope). Предыдущий тест не успел почистить слот — следующий упал.
«Решение оказалось архитектурным, не тестовым. Код самого теста не трогали, а только перевели fixture из shared scope в function scope. SLOT_OVERLAP исчез полностью».
И главный вывод, который он сделал:
«Flakiness as a signal» — тест падал не потому, что был плохо написан, а потому что подсвечивал реальный дефект в дизайне окружения. Если просто пометить его flaky и заглушить, то теряется единственный индикатор проблемы.
Я тогда посмотрел на свой анализатор. И понял: мой FlakyDetector 2.0 был слеп к этому кейсу.
Потому что он смотрел только на код теста, а не на связки между тестами через фикстуры.
Как FlakyDetector 2.0 пропустил SLOT_OVERLAP
Модель flaky_v1_37d.cbm была обучена на 37 признаках: счётчики time.sleep, datetime.now(), глобальные мутации, цикломатическая сложность и так далее.
В кейсе Ariless тест выглядел идеально:
def test_slot_creation(slot_fixture): slot = slot_fixture.create() # чисто, нет антипаттернов assert slot.is_empty()
Ни sleep, ни глобальных переменных, ни сетевых вызовов. Модель выдавала 99% уверенности, что тест стабилен.
А проблема была в фикстуре, которая объявлена где-то в conftest.py:
@pytest.fixture(scope="module") # ← вот он, главный злодейdef slot_fixture(): return SharedSlot() # ← нет yield, нет teardown
И в том, что SharedSlot — мутабельный объект. Тест A что-то в нём поменял, тест B прочитал изменённое состояние. Классическая order dependency.
Без анализа фикстур статический метод никогда бы это не нашёл. Спасибо Ariless — он не просто пожаловался, а дал готовую архитектурную идею.
Что я сделал: Sprint 1 — анализ фикстур (расширение до 42D)
За один спринт я добавил в анализатор 5 новых признаков, которые парсят pytest-фикстуры через AST:
|
Признак |
Что значит |
Почему важно |
|---|---|---|
|
|
Есть фикстура с |
Шанс пересечения состояния между тестами |
|
|
Есть ли |
Без |
|
|
Фикстура возвращает |
Мутабельные объекты в shared scope — яд |
|
|
Флаг |
Неявное внедрение, сложно трассировать |
|
|
Доля «рисковых» фикстур в модуле |
Количественная оценка заражения модуля |
Теперь вектор признаков вырос до 42 измерений.
Что показало обучение.
Я переобучил CatBoost на синтетическом датасете, где перемешал безопасные и рискованные фикстуры. Результат меня поразил:
|
Признак |
Важность |
|---|---|
|
|
66.00% |
|
|
26.81% |
|
|
6.15% |
|
|
1.04% |
|
|
0.00% (в синтетике не было вариации) |
Отсутствие yield оказалось главным маркером флакинесса. Модель сама выучила то, что Ariless эмпирически обнаружил на своём проекте.
Теперь FlakyDetector не просто говорит «тест подозрительный», а может показать пальцем:
⚠️ В модуле используется фикстура
slot_fixtureсscope="module", которая не содержитyieldи возвращает мутабельный объект. Это может приводить к утечке состояния между тестами.
А что дальше? RAG и динамический анализ Sprint 2–3
Комментарий Ariless также содержал важное пожелание:
«Особенно если инструмент сможет отвечать не только «есть order dependency», а конкретно: «тест B падает, потому что тест A не очистил состояние X».»
Статический анализ не может этого сделать — нужны данные с реальных прогонов. Поэтому я добавил:
-
Плагин
pytest-flaky-trail— записывает в SQLite порядок выполнения и результаты тестов, трейсбэки. -
LLM + RAG — берёт контекст упавшего теста (код + метрики), отправляет в локальную модель (Qwen 2.5 Coder 7B) и получает гипотезу о причине и исправлении.
-
ChromaDB — векторизует эти отчёты, чтобы можно было искать семантически похожие проблемы: «покажи все тесты, которые падают из-за незакрытого слота».
Пока это работает в режиме исследования, но уже даёт ответы в стиле:
«Тест
test_create_slotупал, потому что тестtest_use_slot(выполненный ранее) оставил занятым слотslot_123. Рекомендуем перевести фикстуру наscope="function"или добавитьyieldс очисткой.»
Как это выглядит в коде (пример)
Рисковая фикстура, которую теперь находит FlakyDetector 2.0:
import pytest@pytest.fixture(scope="module") # ← признак 37: Truedef shared_state(): return [] # ← признак 39: True, и нет yield → признак 38: Falsedef test_a(shared_state): shared_state.append("a") # мутацияdef test_b(shared_state): assert len(shared_state) == 0 # может упасть, если тест A был раньше
Запускаем сканер:
uv run python scripts/scan_folder.py ./tests/
Вывод:
⚠️ CRITICAL: Фикстура shared_state (test_example.py:5) - scope="module" (shared across tests) - отсутствует yield (нет teardown) - возвращает мутабельный список Рекомендация: добавь yield с очисткой или смени scope на "function"
Огромное спасибо Ariless
Твой комментарий стал триггером для целого направления развития. Если читаешь это — отпишись в комментариях, я хочу публично сказать спасибо. Ты не просто указал на проблему, а дал готовое архитектурное решение и философский принцип «flakiness as a signal», который теперь вшит в ДНК проекта.
Кстати, проект открыт.
GitHub: Artem7898/flakydetector
Документация и research paper — в репозитории, там же лежит обученная модель flaky_v2_42d.cbm.
Установка через uv или Docker — всё как в первой статье, но теперь с новой мощью.
Буду рад лайку и комментарию — это помогает продвигать материалы и подсказывает, что разбирать дальше.
Пиши в комментариях, сталкивался ли ты с нестабильными тестами из-за фикстур? Как ты их ловил? Может, у тебя есть пример, который мы ещё не умеем детектировать?
А если интересна тема анализа кода и ML для тестирования — вот предыдущий разбор про FlakyDetector 1.0 👇 FlakyDetector 2.0 👇
Ссылка на первую статью Ссылка на вторую статью
Сделано с любовью к стабильному CI и к тем, кто не затыкает flaky-тесты костылями.
ссылка на оригинал статьи https://habr.com/ru/articles/1043792/