ИИ-помощь в тестировании: модульные тесты как шлюз для кода, сгенерированного ИИ
Categories:
ИИ-помощь в тестировании решает не проблему покрытия тестами, а проблему моего доверия к коду, сгенерированному ИИ. ИИ за три минуты может выплюнуть большой кусок кода, а человеку нужно два часа, чтобы его полностью просмотреть — «три минуты генерация, два часа ревью» не преувеличение. Моё решение — сменить объект проверки: не читать код построчно, а читать документацию тест-кейсов, и с помощью модульных тестов построить шлюз в локальной среде разработки, позволяя упавшим тест-кейсам следить за ленью и ошибками ИИ за меня.
Маленькие баги — главное поле битвы ИИ-помощи в тестировании
Баги делятся на два типа: легко решаемые и трудно решаемые.
Трудно решаемые баги обычно исходят из дефектов архитектуры или дизайна решения, постепенно превращаясь в исторический балласт, задевающий всё сразу; также они могут быть вызваны третьими сторонами — сторонними библиотеками, ОС, компиляторами — и требуют более глубоких знаний и исследования. Такие сложные проблемы выходят за рамки данной статьи.
Статья фокусируется на мелких багах в жизненном цикле продукта, используя ИИ-помощь в создании тест-кейсов для решения следующих проблем:
- Баги, которые можно быстро обнаружить
- Низкоуровневые баги, исправляемые минимальными правками
- Баги, внесённые при изменениях
- Ошибки в спешке при срочных правках
- Баланс затрат и эффективности целевого тестирования
Большинство багов — это мелкие баги, но они тоже могут вызвать серьезные проблемы. Решение мелких багов на локальной машине экономит огромное количество последующих усилий.
Меняем объект проверки с кода на документацию тест-кейсов
Я пробовал самую смелую идею: поручить ИИ полностью выполнить требование, вообще не просматривая сгенерированный код, как в режиме Copilot Agent — полная отдача контроля. Позже отказался. Коренная причина — нет уверенности внутри. Для логически строгой бинарной программы отсутствие уверенности означает потерю доверия вверх и вниз по потоку, потерю доверия клиента. Поэтому я размышляю над vibe coding: для кода, сгенерированного ИИ, обязательно нужно иметь внутреннюю уверенность.
Проблема в стоимости проверки. ИИ иногда генерирует или меняет слишком много кода — «три минуты генерация, два часа ревью» не преувеличение. Объём тестового кода обычно больше бизнес-кода, и он не всегда учитывается в KPI доставки разработки — это одна из причин, почему многие не хотят писать тесты проактивно. Но факт, что ИИ-ассистент намного умнее меня, уже неоспорим; вместо сопротивления лучше дать ему шлюз.
Смена объекта проверки с кода на документацию тест-кейсов — ключ к разрезанию этого гордиевого узла. В отличие от бизнес-кода, тест-кейсы, сгенерированные ИИ, обычно описывают очень конкретный сценарий, их легко проверять и верифицировать. Многие тест-кейсы даже не нужно читать построчно — достаточно посмотреть, когда они падают. Говорить, что код ИИ вообще не проверяется — преувеличение, но благодаря完善ной документации тест-кейсов можно доказать, что не каждая строка кода требует ручного ревью.
---
title: От проверки кода к проверке документации
---
flowchart TB
A[ИИ генерирует много кода] e1@--> B[Высокая стоимость построчной проверки]
B e2@--> C{Что проверять}
C e3@-->|Код| D[Два часа проверки, нет уверенности]
C e4@-->|Документация тест-кейсов| E[Легко проверять, быстрая верификация]
E e5@--> F[Шлюз модульных тестов]
F e6@--> G[Упавшие тест-кейсы раскрывают проблемы]
classDef risk fill:#FFF3E0,stroke:#E65100,color:#E65100;
classDef ok fill:#E8F5E9,stroke:#2E7D32,color:#1B5E20;
classDef animate stroke:#EF6C00,stroke-width:2px,stroke-dasharray: 9\,5,stroke-dashoffset: 900,animation: dash 25s linear infinite;
class D risk;
class E,F,G ok;
class e1,e2,e3,e4,e5,e6 animate;Методологическая база: Шесть сигм и DMAIC
Шесть сигм (Six Sigma) — это методология улучшения качества, основанная на данных, главная цель которой — снижение вариативности процесса до уровня дефектов 3.4 на миллион, то есть уровня 6σ1.
Она даёт исполняемый процесс DMAIC:
- Define (Определение): анализ требований, преобразование в модель данных, проектирование структур данных и интерфейсов
- Measure (Измерение): мониторинг тест-кейсов, покрытие, производительность, безопасность
- Analyze (Анализ): анализ тест-кейсов и бизнес-кода
- Improve (Улучшение): улучшение тест-кейсов и бизнес-кода
- Control (Контроль): автоматизированные тестовые шлюзы
Сопутствующие технические инструменты включают линтеры, статический анализ, динамический анализ и тестовые фреймворки. Метрик качества кода много: метрики сложности (цикломатическая сложность, когнитивная сложность, глубина вложенности, связность классов), метрики качества (дублирование, длина функций, магические числа), тестовые метрики (покрытие модульными тестами, прохождение интеграционных тестов, эффективность тест-кейсов, покрытие обработки исключений).
Методология и инструменты помогают находить и решать проблемы, но ценой возможного повышения затрат на доставку и снижения эффективности. При ограниченных ресурсах нужно выбрать метод, приносящий устойчивую выгоду в краткосрочной, среднесрочной и долгосрочной перспективе. Мой выбор — использовать модульные тесты как вспомогательный инструмент в процессе разработки.
С помощью ИИ в преобразовании между языками разных ролей разработка может объединить преимущества TDD, BDD, ATDD, быстро генерируя таблицы тест-кейсов и их код, повышая качество доставки.
Тестирование-драйв: TDD/BDD/ATDD и тестируемый код
Различия трёх подходов к тестированию-драйву:
| Измерение | TDD | BDD | ATDD |
|---|---|---|---|
| Источник драйва | Модульные тесты | Сценарии поведения пользователя | Критерии приёмки |
| Участники | Разработчики | Бизнес + разработка | Бизнес + тестирование + разработка |
| Уровень тестов | Модульные тесты | Интеграционные / E2E тесты | Приёмо-сдаточные тесты |
| Примеры инструментов | JUnit, pytest | Cucumber, SpecFlow | FitNesse, Robot Framework |
| Типичный вывод | Отчёт о покрытии кода | Исполняемая документация сценариев | Процент прохождения приёмо-сдаточных тестов |
| Язык | assert add(2,3) == 5 | Естественный язык (Given-When-Then) | Бизнес-язык (таблицы критериев приёмки) |
Тестируемый код имеет четыре ключевых принципа: единственная ответственность (каждая функция делает одну вещь), внедрение зависимостей (передача зависимостей через параметры), изоляция интерфейсов (чёткие типы ввода/вывода), наблюдаемость состояния (предоставление интерфейсов запроса для верификации внутреннего состояния).
В промптах для ИИ я рекомендую прямо писать расширяемость, единственная ответственность, внедрение зависимостей. Я пробовал заставлять ИИ контролировать длину генерируемых функций и файлов — соблюдение было плохим; но сменив на единственную ответственность и внедрение зависимостей, сгенерированный код стал заметно ближе к ожиданиям. Преждевременная оптимизация — корень всех зол; при генерации базовых структур данных и интерфейсов достаточно просить ИИ приоритизировать расширяемость.
Пример кода, удобного для тестирования:
// user.h
typedef struct {
int id;
char name[50];
int age;
} User;
// Абстракция интерфейса памяти
typedef struct {
User* (*get_user)(int id);
int (*save_user)(const User* user);
} UserStorage;
// Создание пользователя (внедрение зависимости хранилища)
User* create_user(const char* name, int age, UserStorage* storage) {
if(strlen(name) >= 50 || age <= 0) return NULL;
User* new_user = malloc(sizeof(User));
new_user->id = generate_user_id();
strncpy(new_user->name, name, 49);
new_user->name[49] = '\0';
new_user->age = age;
if(storage->save_user(new_user) != 0) {
free(new_user);
return NULL;
}
return new_user;
}
Пример кода, неудобного для тестирования:
// Жёсткая связь с операциями БД
void save_user_to_db(User* user) {
MYSQL conn;
mysql_init(&conn);
if(!mysql_real_connect(&conn, "localhost", "root", "pass", "test", 0, NULL, 0)) {
log_error("DB connection failed"); // Зависимость от логирования, трудно тестировать
exit(1); // Прямой выход влияет на выполнение тестов
}
// Сборка SQL и запись в БД
}
Хороший код действует бесшумно, плохой — навязчив. Удобные для тестирования функции имеют чёткие возвращаемые значения, стандартизированную обработку ошибок, явные входные параметры, изолированные побочные эффекты, поддержку внедрения зависимостей.
Код, даваемый ИИ, иногда не соответствует лучшим практикам, есть проблемы со стилем. Хотя он часто решает проблему, реальный проект — не разовая сделка, нужно думать о последующей поддержке. В промптах для генерации кода также нужно подчёркивать читаемость в приоритете, расширяемость, единственную ответственность, внедрение зависимостей. Проблемы стиля кода не рассматриваются подробно, можно обратиться к руководству по стилю Google2.
Ямы и компромиссы реальной разработки
При передаче требований между ролями происходит много раундов преобразования языков, и каждый раунд может дать искажение. Например, клиент говорит «централизованный просмотр», а бэкенд понимает как «один интерфейс возвращает всё» без пагинации. С помощью ИИ для генерации шаблонов языков ролей можно снизить потери информации при ручном преобразовании.
Недоразмышленность — корень многих багов. С помощью ИИ для проектирования сценариев тест-кейсов он не только выполняет ожидаемую задачу, но и сверх ожиданий даёт вдохновение, дополняя детали, которые автор задания не учел. В каждом раунде преобразования языков ролей можно диалогировать с ИИ для устранения пробелов. Markdown — язык, который ИИ понимает лучше всего, ввод и вывод лучше делать в формате Markdown.
У тестового кода есть несколько критичных моментов: изоляция тест-кейсов, точность моков зависимостей, валидность ассертов, граничные значения, обработка исключений, производительность, параллелизм. ИИ ленится, не сгенерирует за раз тест-кейсы, покрывающие все сценарии. Генерация всего тестового кода за раз ставит высокие требования к модели; можно генерировать в несколько подходов.
На примере Claude в Cursor: он неплохо справляется с частью замечаний по тестовому коду, но часто забывает об изоляции тест-кейсов и исключительных сценариях. Добавление user rule может немного снизить лень: при генерации тест-кейсов обращать внимание на документацию тест-кейсов, на изоляцию тест-кейсов. Но он всё равно не сгенерирует все сценарии за раз — нормальные пути, исключительные пути, граничные значения, производительность, параллелизм нужно дозапрашивать в несколько диалогов.
Интеграционные тесты требуют построения тестового окружения. Некоторые тест-кейсы зависят от внешних инструментов, например, микро-сегментация зависит от iptables и ipset — можно использовать Docker для построения тестовых контейнеров. Принцип: как тестирует человек, так и приказываем ИИ реализовать те же автоматизированные шаги. Про безопасностное тестирование — пока основная опора на опыт и анализ, зрелого ИИ-попыта нет.
Один рабочий процесс ИИ-помощи в тестировании
Объединив с дизайн-документом, можно выстроить конвейер от сценариев к тест-кейсам:
---
title: Рабочий процесс ИИ-помощи в тестировании
---
flowchart TB
A[Дизайн-документ] e1@--> B[Генерация сценариев приёмо-сдаточного тестирования]
B e2@--> C[Генерация структур данных и интерфейсов]
C e3@--> D[Генерация таблицы тест-кейсов на стороне клиента]
D e4@--> E[Генерация кода тест-кейсов]
D e5@--> F[Генерация бизнес-кода]
E e6@--> G[Локальный шлюз: тест-кейсы проходят]
F e7@--> G
G e8@--> H{Есть упавшие тест-кейсы}
H e9@-->|Да| E
H e10@-->|Нет| I[Отдача на тестирование]
classDef input fill:#E3F2FD,stroke:#1565C0,color:#0D47A1;
classDef gen fill:#E8F5E9,stroke:#2E7D32,color:#1B5E20;
classDef gate fill:#FFF3E0,stroke:#E65100,color:#E65100;
classDef animate stroke:#EF6C00,stroke-width:2px,stroke-dasharray: 9\,5,stroke-dashoffset: 900,animation: dash 25s linear infinite;
class A input;
class B,C,D,E,F gen;
class G,H,I gate;
class e1,e2,e3,e4,e5,e6,e7,e8,e9,e10 animate;Промпты для каждого шага можно организовать так: по продуктовому дизайн-документу сгенерировать проверяемые сценарии приёмо-сдаточного тестирования; по тестовым сценариям сгенерировать структуры данных и интерфейсы; по тестовым сценариям и интерфейсам сгенерировать таблицу тест-кейсов на стороне клиента; по интерфейсам и таблице тест-кейсов сгенерировать код тест-кейсов; по тестовым сценариям сгенерировать бизнес-код и обеспечить прохождение тест-кейсов.
Здесь есть скрытая опасность: ИИ может и править бизнес-код под тестовый код, и наоборот — править тестовый код под бизнес-код. Если ошибки есть в обоих, ИИ запутается, и с ростом числа диалогов тестовый и бизнес-код станут всё более хаотичными — в этот момент необходимо ручное вмешательство.
Жадный промпт — контрпример: требовать от ИИ за раз покрыть нормальные, исключительные, граничные случаи, параллелизм и сгенерировать код. Такие запросы легко прерываются. Правильный подход — в Cursor разбивать сценарии, использовать несколько запросов для пакетной генерации. Тест-кейсы производительности требуют много данных — их лучше отнести к этапу системного тестирования: сначала пусть ИИ напишет генератор случайных данных, а потом на их основе построит тест-кейсы.
Итог
Этот рабочий процесс по сравнению с разработкой без ИИ даёт больше документации и тестового кода, и проверить каждое слово практически невозможно. Но документацию проверять легче, чем код, особенно таблицы тест-кейсов — они дают точное руководство для генерации тест-кейсов. В реальной разработке я иногда смотрю только упавшие тест-кейсы, подтверждая проблему через их отладку.
Направления улучшения три: в подпроцессе дизайна есть пересечения, сценарии приёмо-сдаточного тестирования могут зависеть от структур данных и интерфейсов; таблицу тест-кейсов на стороне клиента разработчик следит сам; перед отдачей на тестирование нужно снова сгенерировать детальные тестовые сценарии, включающие детали бизнес-кода.
Итоговая выгода не в цифрах покрытия тестами, а в доверии: благодаря этому локальному шлюзу модульных тестов я наконец получил внутреннюю уверенность в коде, сгенерированном ИИ, вместо того чтобы бездумно пускать vibe-код в продакшн.
Google Style Guide — публичное руководство по стилю кода от Google. ↩︎