Тестирование генеративного кода: как не запустить баг в продакшн, когда код пишет машина

Ты не одинок. Каждый день сотни разработчиков сталкиваются с одной и той же проблемой: генеративный код — быстрый, удобный, но иногда странно работает. Он пишет функцию, которая «вроде бы» делает то, что ты просил, но при этом ломает логику авторизации, не обрабатывает пустые строки или внезапно начинает генерировать SQL-инъекции. И всё это — в коде, который ты не писал. Ты не можешь просто перечитать его, как обычный — он слишком объёмный, слишком «машинный». Как проверить, что этот код не убьёт твой сервис?

Тестирование генеративного кода — это не про то, чтобы «проверить, работает ли». Это про то, чтобы понять, как он думает. И как его заставить думать правильно.

Почему обычные тесты не справляются

Ты привык писать юнит-тесты: функция принимает X — возвращает Y. Просто, понятно, надёжно. Но когда код генерируется, он часто выглядит так:

  • «Создай функцию для загрузки файла с проверкой расширения и лимита размера» — и в ответ получаешь 40 строк кода с кучей вложенных условий, кастомными исключениями и неочевидными хелперами.
  • «Сделай API-эндпоинт для обновления профиля» — и он генерирует POST-запрос с 12 полями, 5 из которых не нужны, а одно — уязвимо к XSS.

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

Настоящая проблема: генеративный код не «ошибается» — он неправильно понимает контекст. Он не знает, что в твоём проекте JWT-токен должен обновляться каждые 15 минут. Он не знает, что твой бэкенд не поддерживает batch-запросы. Он не знает, что твоя база данных не умеет JSON-индексы. Он просто строит шаблон на основе того, что видел в обучающих данных.

Новые методики: что меняется

Три ключевых подхода, которые реально работают — не теория, а то, что я видел в продакшене у команд, которые не теряли клиентов из-за генерированного кода.

1. Тестирование на основе контекста (Context-Aware Testing)

Это не про «функция работает» — это про «функция работает в твоём проекте».

Ты создаёшь «контекстный профиль»: набор правил, специфичных для твоего стека.

  • Все API-эндпоинты должны возвращать 401 при отсутствии токена — даже если генератор забыл.
  • Все SQL-запросы должны использовать параметризованные переменные — никакого конкатенации строк.
  • Все обработчики ошибок должны логировать только ID запроса, а не данные пользователя.

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

Пример: генератор написал:

const query = `SELECT * FROM users WHERE email = '${email}'`;

Обычный линтер молчит. Контекстный линтер — кричит: «Нарушение политики: конкатенация строки в SQL-запросе. Используй параметризованные запросы.»

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

2. Симуляция атак (Fuzzing для генеративного кода)

Ты не тестируешь функцию на корректных входах — ты тестируешь её на плохих.

Генератор написал функцию для загрузки аватарки. Ты подаёшь на вход:

  • Файл размером 5 ГБ
  • Файл с именем ../../../etc/passwd
  • Файл с MIME-типом text/html, но с содержимым JS
  • Пустой буфер
  • Бинарные данные, которые выглядят как PNG, но на самом деле — архив

И смотришь, что происходит. Не «работает ли», а «не ломается ли».

Это называется fuzzing — и это не новость. Но раньше его применяли к API или бинарникам. Сейчас — к генерированному коду. И это работает. Особенно если ты добавляешь автоматический сбор метрик: сколько раз код упал, сколько раз вернул 500, сколько раз пропустил проверку.

Совет: запускай fuzzing не один раз, а в CI/CD. Каждый раз, когда генератор обновляет код — ты автоматически бросаешь на него 500 случайных, но опасных входов. И если он упал хотя бы раз — сборка падает.

3. Проверка на «поведенческое соответствие» (Behavioral Alignment)

Это самое важное. Ты сравниваешь поведение генерированного кода с поведением твоего старого, проверенного кода.

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

  • Сколько времени занимает генерация?
  • Какие заголовки он возвращает?
  • Какие поля он включает в payload?
  • Какой алгоритм подписи использует?
  • Как он обрабатывает истёкший токен?

Ты запускаешь оба варианта — старый и новый — на одинаковых входах, и сравниваешь выходы. Не по коду, а по результату. Если новый код выдаёт токен с другим TTL — ты не можешь его запускать. Даже если он «выглядит красивее».

Это требует инструмента, который умеет:

  1. Запускать оба варианта кода в изолированной среде (Docker-контейнеры, например).
  2. Подавать идентичные входные данные.
  3. Сравнивать выходы: статусы, тела ответов, логи, время выполнения.
  4. Выдавать отчёт: «Новый код работает на 12% медленнее. Возвращает поле ‘iss’ с другим значением. Не совпадает по TTL.»

Такой подход позволяет тебе заменять куски кода без страха. Ты не доверяешь генератору — ты доверяешь результату.

Сравнение методик: что подойдёт тебе

Вот как эти три методики соотносятся по сложности, скорости и эффективности:

Методика Сложность внедрения Скорость проверки Эффективность против багов Подходит для
Контекстный линтер Средняя Секунды Высокая (правила) Команды с чёткими стандартами
Fuzzing Средняя-высокая Минуты Очень высокая (непредсказуемые сценарии) API, системы с внешними входами
Поведенческое соответствие Высокая Минуты-часы Критически высокая (точное сравнение) Замена критичных модулей (аутентификация, платежи)

Если ты только начинаешь — начни с контекстного линтера. Он даст быстрый эффект. Если твой продукт — API с внешними клиентами — добавь fuzzing. Если ты заменяешь ядро системы (аутентификация, оплата, расчёты) — без поведенческого сравнения не обойтись.

Частые ошибки

Вот что ломает команды, даже если они знают про эти методики:

  • Проверяют только «работает ли». Генератор написал функцию — запустили, вроде не упало — закрыли. Нет. Надо проверять, как он работает в твоём окружении.
  • Игнорируют контекст. Генератор не знает, что в твоём проекте все даты в UTC, а не в локальном часовом поясе. Если ты не дашь ему этот контекст — он будет генерировать баги.
  • Не интегрируют в CI/CD. Тесты — это не раз в неделю. Если ты не запускаешь проверки автоматически при каждом новом коде — ты просто не контролируешь процесс.
  • Доверяют метрикам производительности без проверки. Генератор написал код, который в 2 раза быстрее — но использует 10x больше памяти. Это не победа. Это бомба.
  • Тестируют только «хорошие» сценарии. Если ты не проверяешь, как код ведёт себя при пустых, битых, вредоносных входах — ты не тестируешь. Ты молишься.

Что выбрать в зависимости от ситуации

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

  • Ситуация: ты генерируешь вспомогательные функции (хелперы, утилиты, валидаторы) — используй контекстный линтер. Он быстро, дешево и эффективно ловит нарушения стиля и безопасности.
  • Ситуация: ты генерируешь API-эндпоинты или микросервисы — добавь fuzzing. Ты не знаешь, какие запросы придут от клиентов — поэтому ты должен проверить, как код реагирует на всё, что можно придумать.
  • Ситуация: ты заменяешь критичный модуль (аутентификация, платежи, расчёты) — обязательно используй поведенческое соответствие. Даже если генератор написал «лучше», если он ведёт себя иначе — ты не можешь его запускать.
  • Ситуация: ты используешь генерацию для быстрого прототипирования — используй контекстный линтер + fuzzing. Но не запускай в продакшн без ручной проверки. Прототип — это не продукт.

Как лучше сделать: пошаговый план

Вот как внедрить это в твой процесс на практике — без лишней бюрократии.

  1. Собери 5–10 правил контекста. Что обязательно должно быть в твоём коде? Например: «Все SQL-запросы — параметризованные», «Все токены — с TTL 15 минут», «Все логи — без персональных данных». Запиши их в JSON-файл.
  2. Напиши простой линтер. Это может быть скрипт на Python или Node.js, который читает твой контекст и проверяет генерированный код на соответствие. Не нужно сложной системы — достаточно regex-поиска по ключевым словам и структурам.
  3. Настрой fuzzing для API. Используй инструмент вроде python-fuzz или OWASP ZAP. Подготовь 10–20 тестовых сценариев с опасными входами. Запускай их через CI/CD каждый раз, когда генератор обновляет код.
  4. Для критичных модулей — создай «бенчмарк». Возьми старый код, запусти его на наборе входов, сохрани результаты (ответы, время, память). Запусти новый код на тех же входах. Сравни. Если отличается — не деплой.
  5. Интегрируй всё в CI/CD. Проверки должны быть обязательными. Без них — сборка не проходит. Не делай «опционально». Это не тесты — это безопасность.

Не жди идеальной системы. Начни с одного правила. Потом добавь один fuzz-тест. Потом один бенчмарк. Через месяц у тебя будет система, которая ловит 90% проблем до того, как они попадут в продакшн.

Рекомендации: что делать прямо сейчас

Если ты читаешь это — значит, ты уже используешь генеративный код. Не жди «лучшего момента». Делай сейчас:

  • Сегодня: выпиши 3 правила, которые должны быть в любом коде, который ты запускаешь. Например: «Нет конкатенации в SQL», «Нет токенов без TTL», «Все ошибки логируются с request-id».
  • Завтра: напиши простой скрипт, который проверяет генерированный код на эти правила. Даже если он на 5 строк — это уже защита.
  • На этой неделе: выбери один API-эндпоинт, который генерировался, и запусти на нём 5 fuzz-тестов с опасными входами. Посмотри, что происходит.
  • На следующей неделе: возьми один критичный модуль (аутентификация, расчёт скидок, генерация отчётов) и сравни его поведение с генерированным аналогом. Не смотри на код — смотри на результат.

Ты не должен стать экспертом по тестированию. Ты должен просто не выпускать в продакшн код, который может сломать твой сервис. Это не про сложность — это про дисциплину.

Итог: ты не контролируешь код — ты контролируешь результат

Генеративный код — это не волшебная палочка. Он — инструмент. И как любой инструмент, он может помочь, а может убить. Ты не можешь доверять ему. Ты можешь только проверять, что он делает.

Тестирование генеративного кода — это не про то, чтобы понять, как он работает. Это про то, чтобы понять, что он делает. И если он делает что-то не то — ты должен остановить его, даже если он «выглядит красиво».

Ты не должен писать больше кода. Ты должен проверять больше результатов.

Начни с одного правила. Проверь один модуль. Запусти один fuzz-тест. Сравни один бенчмарк. И через неделю ты увидишь: твои баги в продакшене начали исчезать. Не потому что генератор стал лучше — потому что ты перестал слепо ему доверять.

Информация в статье носит ознакомительный характер. Принятие решений о внедрении кода в продакшн должно основываться на результатах тестирования и консультации с командой безопасности и разработки.

dfncfg.ru — цифровой мир и технологии