Почему тестирование генеративного ИИ ломает привычную проверку
Сравните ответ с фиксированной строкой — и на втором прогоне тест упадёт по причине, никак не связанной с вашим продуктом. Ослабьте проверку до вхождения подстроки — и она пропустит вывод, который неочевидно неверен. Пользы нет ни от того, ни от другого, а метания между этими вариантами — привычный путь к выводу, что генеративные функции невозможно тестировать.
Что проверять вместо этого
| Свойства | Корректный JSON, обязательные поля на месте, укладывается в ограничение по длине, ссылается на реально существующий источник. Проверяемо и устойчиво от прогона к прогону. |
| Отказы | Модель отказывает в том, в чём должна отказывать, и делает это стабильно. Часто самая ценная проверка во всём наборе. |
| Привязка к контексту | Каждое утверждение прослеживается до чего-то в переданном контексте. Ловит выдумки, не оценивая стиль. |
Доли, а не отдельные результаты
Один прогон не говорит ни о чём. Прогоните один и тот же ввод много раз и отслеживайте, в какой доле случаев свойство выполняется. Ваша единица измерения — доля, а сигналом служит её изменение, а не отдельное падение.
Это меняет и само понятие регрессии. Вопрос никогда не в том, «идеально ли это», а в том, «стало ли после этого изменения хуже, чем неделю назад».
Как выбрать свойства для проверки
Сложность здесь не в механике, а в том, чтобы решить, что проверять, когда проверить сам ответ невозможно. Удобный заход — спрашивать не о том, что сделало бы вывод хорошим, а о том, что сделало бы его непригодным.
Непригодность конкретна: некорректный вывод, который вызывающая сторона не может разобрать; ссылка на то, чего не существует; отказ там, где должен был быть ответ; ответ там, где должен был быть отказ; содержимое, превышающее ограничение на следующем шаге. Каждый из этих случаев бинарен и проверяем, и каждый соответствует реальному инциденту, а не мнению о качестве.
«Хорошо», напротив, — оценка, которая меняется от проверяющего к проверяющему и которую не стоит зашивать в блокирующее условие. Отслеживайте её, если хотите, силами ручного разбора выборки, но держите вне конвейера, где она породит споры, а не решения.
Та часть, которая тестируется обычным способом
Бóльшая часть продукта с генеративной функцией — обычное детерминированное ПО: аутентификация, хранение данных, ограничение частоты запросов, интерфейс вокруг самой функции. Всё это нужно тестировать традиционно, а если считать весь продукт недетерминированным из-за одной его части, вы теряете много дешёвого покрытия.
Проведите границу явно. Внутри неё — свойства и доли. Снаружи — обычные проверки на работающем приложении.
Терминал
npm install -g @testsprite/testsprite-cli
testsprite setup
Если вы предпочитаете ничего не устанавливать, панель управления делает то же самое. Всё остальное, что умеет командная строка, описано в репозитории CLI.
Есть два пути, и выбор на деле сводится к тому, кто владеет конвейером. Подключение GitHub App из панели управления вообще не требует изменений в вашем репозитории, потому что оно реагирует на тот деплой, который ваша сборка выпускает и так. Добавление шага GitHub Actions помещает проверку в репозиторий, где она проходит ревью наравне с остальной сборкой.
Что TestSprite покрывает вокруг этой функции
Детерминированное большинство продукта — у большинства команд это несравнимо бóльшая его часть. Аутентификация, хранение данных, ограничение частоты запросов, интерфейс вокруг функции и все сценарии, по которым реально проходит пользователь, — это обычное ПО, и тестируется оно традиционно. Auto-Authentication поддерживает сессии живыми на протяжении всего прогона, Dynamic Variables переносят значения между вызовами, Dependency Chains выводят порядок выполнения, а Auto-Cleanup удаляет ровно то, что создал прогон.
Покрытие генерируется из вашего продукта и уточняется обычным языком, а выполняется на развёрнутом приложении при каждом изменении, так что те части системы, которые можно проверить точно, проверяются точно.
Самой генеративной функции нужны свойства и доли — это отдельная обвязка со своим представлением о том, что значит «пройдено». Именно явно проведённая граница удерживает команду и от применения статистических методов к детерминированному коду, и от полного отказа от проверок из-за того, что точные сравнения постоянно падали.
Можно ли поручить оценку вывода отдельному ИИ?
Подход распространённый и рабочий, но с оговоркой: у оценивающей модели свои режимы отказа, и её саму нужно калибровать. Сверьте её с человеческой оценкой на выборке, прежде чем ей доверять.
Сколько нужно прогонов, чтобы доля была осмысленной?
Столько, чтобы число переставало меняться, когда вы добавляете новые. Начните с десятков, а не сотен, и следите за стабильностью.
Должны ли генеративные тесты блокировать слияние?
Сильное падение доли отказов — да. Небольшие колебания качества — нет, иначе конвейер превратится в шум.
А как насчёт стоимости?
Повторные прогоны стоят денег. При каждом изменении берите выборку вместо полного набора, а полный набор запускайте по расписанию.
Тестирование промптов — это то же самое?
Смежная вещь. Изменения промптов — самая частая причина изменения долей, и это хороший довод в пользу того, чтобы относиться к промптам как к версионируемым артефактам.
Свойства и доли, а не равенство.
Тестирование генеративного ИИ заменяет точные сравнения проверкой свойств, отказов и привязки к контексту, измеряемой как доли по множеству прогонов. Детерминированное большинство продукта тестируйте обычным способом и проводите границу явно.