искусственный интеллект · разработка · тестирование · CI/CD · regression testing2 октября в 10:33 · 4 мин

Регрессионные тесты для кода ИИ: инструмент проверки поведения функции

Когда нейросеть пишет код и утверждает, что все тесты зелёные, это не всегда повод для радости. Ошибки модели часто скрываются за отсутствием тестов на граничные условия, которые сама же модель и пропустит. Новое решение на базе дифференциального тестирования позволяет автоматизированно выявлять изменения в поведении функций, сравнивая старый и новый код на множестве входов, а не полагаясь на формальные проверки.

# Как поймать «тихую» регрессию в коде, написанном ИИ

Разработчики всё чаще используют генеративные модели для ускорения работы: от написания утилит до рефакторинга сложной логики. Однако автоматизированный код часто содержит скрытые уязвимости. Классические системы контроля качества — линтеры и статические анализаторы — могут молчать, а автоматически сгенерированные тесты, написанные той же моделью, подтверждают лишь то, что код работает так, как его *заставили* работать, а не так, как требовалось изначально.

*Здесь важно понимать: если модель написала и код, и тесты, они формируют замкнутый круг самоподтверждения. Один «зелёный» PR может на следующий день обернуться багом, если требования были неверно интерпретированы.*

Проблема: когда одна строка меняет всё

Рассмотрим классический пример: функция расчёта скидки. В оригинальной версии код содержал явную валидацию: отрицательные проценты запрещались. Модель, стремясь «упростить» код, удалила проверку на отрицательные значения, сохранив остальные правила. Разница в одной строке.

Стандартный CI (система непрерывной интеграции) реагирует спокойно. Тесты, написанные моделью, проверяют случайные значения или явно указанные кейсы. Если в них нет отрицательного процента, тесты проходят. Ревьюер видит аккуратный дифф в одну строку и утверждает merge. Через неделю в продакшоне функция начинает принимать отрицательные проценты, приводя к финансовым потерям.

Решение заключается в подходе, называемом differential testing (дифференциальное тестирование). В отличие от property-based тестирования, где нужно вручную описывать свойства системы, здесь задача тривиальна: для любого входного данных новая версия функции должна давать тот же результат и те же исключения, что и старая версия. Если поведение изменилось, это расхождение фиксируется автоматически.

Архитектура проверки: от диффа к контрапримеру

Инструмент работает по следующему алгоритму: 1. Анализ изменений: Система определяет изменённые файлы через git diff и вытаскивает именно модифицированные функции верхнего уровня, игнорируя мелкие изменения внутри методов или переименования переменных. 2. Генерация входов: Для каждого параметра функции генерируются тестовые данные. Если в коде есть типизация (type hints), генератор использует их. Если типов нет, модель помогает предложить стратегию генерации, но при этом исполняемый код не запускается, а используется только её структурированный вывод (JSON-спецификация). 3. Сравнение: Старая и новая версии функций запускаются в изолированных процессах на одних и тех же входах. Сравниваются не только возвращаемые значения, но и факт выброса исключений.

Главный вызов такого подхода — минимизация ложных срабатываний. Инструмент учитывает нюансы: сравнение NaN с NaN считается равенством, операции с плавающей точкой проверяются через math.isclose, а недетерминированные функции (использующие случайные числа) помечаются как skipped, чтобы не блокировать мерж из-за артефакта рандома.

Технические ловушки и их решение

Разработка системы контроля качества для ИИ потребовала решения множества сложных проблем:

* Граничные значения: Случайная генерация чисел редко находит баги в узких диапазонах. Поэтому к случайным тестам добавлен детерминированный перебор граничных значений (например, -1, 0, 1, 100 и их малые дробные аналоги). * Мутация аргументов: Если функция модифицирует свой аргумент (например, добавляет элемент в список), а новый код принимает уже изменённый список, сравнение выдаст ошибку, хотя логики нет. Решение — создание глубоких копий входных данных перед запуском версий. * Таймауты: На платформах вроде Windows отсутствуют стандартные механизмы прерывания потока для блокирующих операций. Реализован двухуровневый таймаут: сначала прерывание кода, а при его неэффективности — принудительное завершение процесса. * Безопасность: Использование локальной модели (через Ollama) вместо облачного API позволяет избежать утечки кода и снижает задержки, сохраняя при этом воспроизводимость результатов за счёт фиксированных зерн (seed).

Роль искусственного интеллекта в проверке

Хотя ядро инструмента базируется на математическом сравнении входов, искусственный интеллект остаётся важным дополнением. AI-ревьюеры анализируют семантические изменения кода и дают оценку риска. Однако, если дифференциальное тестирование выявляет конкретный контрпример (input, на котором поведение разошлось), оценка ИИ блокируется. Факт расхождения в поведении имеет приоритет над любым текстовым описанием от модели.

Таким образом, мы получаем гибридную систему: скорость и понимание контекста от нейросетей и строгая математическая достоверность от сравнения входов. Это позволяет ловить регрессии, которые могли бы ускользнуть от внимания даже внимательного разработчика.

Первоисточники

Habr AI ↗
← Вернуться в эфир