В монорепозитории изменение одного модуля запускает весь набор тестов. Как механизм определяет минимальный набор затронутых тестов?
Для этого применяют анализ влияния изменений — Test Impact Analysis. Механизм сопоставляет изменённые исходные компоненты с зависимостями тестов и запускает тесты, которые потенциально проверяют затронутое поведение.
Это ускоряет обратную связь, но не гарантирует полноту само по себе: динамические зависимости, рефлексия, конфигурация и неполная карта связей могут привести к пропуску релевантного теста. Поэтому периодически выполняют полный прогон для проверки качества отбора.
По мере роста монорепозиториев полный запуск тестов стал занимать слишком много времени даже при небольшом изменении. Последовательный запуск всего набора расходует ресурсы и задерживает обнаружение ошибок, хотя значительная часть тестов не связана с изменённым кодом.
Анализ влияния изменений возник как способ использовать уже существующие зависимости между кодом, сборочными модулями и тестами. Его цель — сократить объём проверки без постоянного ручного составления списков тестов.
Изменение одного модуля может затрагивать только часть системы, но CI по умолчанию запускает весь набор. Если запускать лишь тесты из изменённого каталога, можно пропустить тесты другого уровня, которые используют этот модуль через косвенную зависимость.
Слишком агрессивное сокращение прогона создаёт риск ложного ощущения безопасности: сборка зелёная не потому, что система корректна, а потому, что нужный тест не был выбран. Слишком консервативный отбор сохраняет полноту, но почти не уменьшает время CI.
Механизм строит или использует граф зависимостей. Его узлами могут быть исходные файлы, библиотеки, сервисы, схемы, конфигурации и тесты, а рёбрами — прямое или косвенное использование. После изменения узла вычисляется транзитивное множество зависимых тестов.
Источником связей служат статический анализ импортов, информация сборочной системы, история запусков, явные метаданные тестов или их комбинация. Надёжнее всего начинать с консервативного статического графа, а динамические сведения использовать для уточнения, а не для безусловного исключения тестов.
Практическая схема обычно включает следующие правила:
Главный компромисс — скорость против полноты. Отбор можно сделать более точным, но чем сложнее модель зависимостей, тем выше стоимость её поддержки и риск ошибок при динамическом связывании.
Важно отличать анализ влияния изменений от кэширования результатов. Кэш отвечает на вопрос, можно ли повторно использовать результат уже выполненного теста при тех же входах и окружении. Анализ влияния отвечает на вопрос, какие тесты вообще необходимо запускать после конкретного изменения.
В монорепозитории изменение общей библиотеки запускало несколько десятков тысяч тестов. Команда рассмотрела три варианта: запускать только тесты из изменённого каталога, запускать полный набор на каждый коммит или внедрить отбор по графу зависимостей.
Первый вариант был самым быстрым, но пропускал потребителей библиотеки из других каталогов. Второй сохранял максимальную полноту, однако обратная связь приходила слишком поздно. Третий вариант потребовал интеграции со сборочной системой и правил для общих компонентов, зато позволил выбирать транзитивно зависимые тесты.
Был выбран третий подход. Для изменений библиотек и исходного кода использовали граф зависимостей, для изменений глобальной конфигурации применяли расширенный набор, а полный прогон оставили обязательным на расписании. Это уменьшило обычный объём CI, при этом контроль полноты сохранился за счёт регулярной проверки полного набора и безопасного fallback при неопределённости.
Потому что тест может находиться в другом модуле, но использовать изменённый компонент через несколько уровней зависимостей. Отбор только по расположению файла учитывает структуру репозитория, а не фактическое влияние изменения. Нужен анализ транзитивных зависимостей либо безопасное расширение набора до уровня общей подсистемы.
Такое изменение часто влияет на зависимости, которые обычный граф исходного кода не описывает полностью. Поэтому для глобальной конфигурации, схемы базы, формата сообщений или общей инфраструктуры следует запускать заранее определённый расширенный набор, а при высокой неопределённости — полный прогон. Экономия времени не должна достигаться ценой систематического пропуска критичных проверок.
Нужно периодически сопоставлять результаты сокращённого и полного прогонов на одинаковом состоянии кода. Если полный прогон обнаруживает дефект, который не был покрыт выбранным набором, анализируют пропущенную зависимость и корректируют граф или правило расширения. Полный прогон также должен выполняться после изменений самого механизма отбора и по расписанию, поскольку его корректность нельзя доказать только успешными сокращёнными запусками.