Почему тестирование генеративного ИИ ломает привычную проверку

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

Что проверять вместо этого

СвойстваКорректный JSON, обязательные поля на месте, укладывается в ограничение по длине, ссылается на реально существующий источник. Проверяемо и устойчиво от прогона к прогону.
ОтказыМодель отказывает в том, в чём должна отказывать, и делает это стабильно. Часто самая ценная проверка во всём наборе.
Привязка к контекстуКаждое утверждение прослеживается до чего-то в переданном контексте. Ловит выдумки, не оценивая стиль.

Доли, а не отдельные результаты

Один прогон не говорит ни о чём. Прогоните один и тот же ввод много раз и отслеживайте, в какой доле случаев свойство выполняется. Ваша единица измерения — доля, а сигналом служит её изменение, а не отдельное падение.

Это меняет и само понятие регрессии. Вопрос никогда не в том, «идеально ли это», а в том, «стало ли после этого изменения хуже, чем неделю назад».

Как выбрать свойства для проверки

Сложность здесь не в механике, а в том, чтобы решить, что проверять, когда проверить сам ответ невозможно. Удобный заход — спрашивать не о том, что сделало бы вывод хорошим, а о том, что сделало бы его непригодным.

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

«Хорошо», напротив, — оценка, которая меняется от проверяющего к проверяющему и которую не стоит зашивать в блокирующее условие. Отслеживайте её, если хотите, силами ручного разбора выборки, но держите вне конвейера, где она породит споры, а не решения.

Та часть, которая тестируется обычным способом

Бóльшая часть продукта с генеративной функцией — обычное детерминированное ПО: аутентификация, хранение данных, ограничение частоты запросов, интерфейс вокруг самой функции. Всё это нужно тестировать традиционно, а если считать весь продукт недетерминированным из-за одной его части, вы теряете много дешёвого покрытия.

Проведите границу явно. Внутри неё — свойства и доли. Снаружи — обычные проверки на работающем приложении.

Терминал

npm install -g @testsprite/testsprite-cli
testsprite setup

Если вы предпочитаете ничего не устанавливать, панель управления делает то же самое. Всё остальное, что умеет командная строка, описано в репозитории CLI.

Есть два пути, и выбор на деле сводится к тому, кто владеет конвейером. Подключение GitHub App из панели управления вообще не требует изменений в вашем репозитории, потому что оно реагирует на тот деплой, который ваша сборка выпускает и так. Добавление шага GitHub Actions помещает проверку в репозиторий, где она проходит ревью наравне с остальной сборкой.

Что TestSprite покрывает вокруг этой функции

Детерминированное большинство продукта — у большинства команд это несравнимо бóльшая его часть. Аутентификация, хранение данных, ограничение частоты запросов, интерфейс вокруг функции и все сценарии, по которым реально проходит пользователь, — это обычное ПО, и тестируется оно традиционно. Auto-Authentication поддерживает сессии живыми на протяжении всего прогона, Dynamic Variables переносят значения между вызовами, Dependency Chains выводят порядок выполнения, а Auto-Cleanup удаляет ровно то, что создал прогон.

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

Самой генеративной функции нужны свойства и доли — это отдельная обвязка со своим представлением о том, что значит «пройдено». Именно явно проведённая граница удерживает команду и от применения статистических методов к детерминированному коду, и от полного отказа от проверок из-за того, что точные сравнения постоянно падали.

Можно ли поручить оценку вывода отдельному ИИ?

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

Сколько нужно прогонов, чтобы доля была осмысленной?

Столько, чтобы число переставало меняться, когда вы добавляете новые. Начните с десятков, а не сотен, и следите за стабильностью.

Должны ли генеративные тесты блокировать слияние?

Сильное падение доли отказов — да. Небольшие колебания качества — нет, иначе конвейер превратится в шум.

А как насчёт стоимости?

Повторные прогоны стоят денег. При каждом изменении берите выборку вместо полного набора, а полный набор запускайте по расписанию.

Тестирование промптов — это то же самое?

Смежная вещь. Изменения промптов — самая частая причина изменения долей, и это хороший довод в пользу того, чтобы относиться к промптам как к версионируемым артефактам.

Коротко

Свойства и доли, а не равенство.

Тестирование генеративного ИИ заменяет точные сравнения проверкой свойств, отказов и привязки к контексту, измеряемой как доли по множеству прогонов. Детерминированное большинство продукта тестируйте обычным способом и проводите границу явно.