6 мин

Запрос Claude Code для генерации тестов пограничных случаев

Освойте шаблон запроса Claude Code, который генерирует высокосигнальные тесты, фокусируясь на границах, режимах отказа и инвариантах — вместо множества happy-path проверок.

Запрос Claude Code для генерации тестов пограничных случаев

Почему генерация только happy-path тестов тратит время

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

При типичном запросе на генерацию тестов для Claude Code модель склонна зеркалить примерные входы, которые она видит. Вы получаете вариации, которые выглядят иначе, но покрывают одинаковое поведение. Результат — большой набор с тонким покрытием там, где это важно.

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

Генерация множества happy-path тестов обычно даёт несколько явных симптомов:

  • Многие тесты отличаются только метками входов, а не тем, что может сломаться.
  • Утверждения поверхностны («не null», «статус 200») вместо проверки смысла.
  • Настройка тяжелее самого тестируемого поведения, поэтому люди перестают обновлять тесты.
  • Покрытие кажется высоким, но краевые случаи не затронуты.

Представьте функцию, которая применяет код скидки. Happy-path тесты подтверждают, что «SAVE10» снижает цену. Настоящие баги прячутся в другом месте: нулевые или отрицательные цены, просроченные коды, пограничные случаи округления или максимальные лимиты скидки. Именно эти случаи приводят к неверным итогам, недовольным клиентам и откатам в полночь.

Цель — перейти от «больше тестов» к «лучшим тестам», нацелившись на три направления: границы, режимы отказа и инварианты.

Три цели: границы, режимы отказа, инварианты

Если вы хотите высокосигнальные модульные тесты, перестаньте просить «ещё тестов» и начните просить три конкретных типа. Это суть запроса для Claude Code, который даёт полезное покрытие вместо груды «работает на нормальном вводе» проверок.

1) Границы (где прячутся баги)

Границы — это края того, что код принимает или производит. Многие реальные дефекты — off-by-one, состояние пустоты или таймауты — не проявляются на happy path.

Думайте о минимумах и максимумах (0, 1, максимальная длина), пустом vs присутствующем ("", [], nil), off-by-one (n-1, n, n+1) и временных лимитах (рядом с порогом).

Пример: если API принимает «до 100 элементов», протестируйте 100 и 101, а не только 3.

2) Режимы отказа (докажите, что система валится безопасно)

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

Пример: когда запрос к базе данных падает, возвращает ли функция понятную ошибку и избегает ли записи частичных данных?

3) Инварианты (правила, которые всегда должны выполняться)

Инварианты — это истины, которые должны оставаться верными до и после вызова. Они превращают расплывчатую корректность в чёткие утверждения.

Примеры:

  • «Баланс никогда не становится отрицательным» после любой попытки снятия.
  • «ID уникальны», даже если вы быстро создаёте предметы.
  • «При ошибке состояние не меняется» (нет новых строк, флаги не переключены).

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

Подготовка: извлеките небольшой контракт перед написанием тестов

Если вы просите тесты слишком рано, обычно получаете груду вежливых «работает как ожидается» проверок. Простое исправление — сначала написать маленький контракт, а затем генерировать тесты из этого контракта. Это самый быстрый способ превратить запрос Claude Code в нечто, что действительно находит баги.

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

Шаблон контракта на 5–10 строк

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

  • Inputs: типы, допустимые диапазоны и что считается «пустым» или «отсутствующим».
  • Output: возвращаемое значение или форма ошибки, и что гарантирует «успех».
  • Side effects: изменения состояния, строки БД, сетевые вызовы, файлы, логи.
  • Assumptions: вещи, которые вызывающие часто путают (таймзона, кодировка, аутентификация, порядок).
  • «Не должно случаться»: краш, молчаливая потеря данных, двойное списание, частичные записи.

Как только контракт готов, просканируйте его на предмет мест, где реальность может нарушить ваши допущения. Они становятся пограничными случаями (min/max, ноль, переполнение, пустые строки, дубликаты) и режимами отказа (тайм‑ауты, отказ доступа, нарушение уникальности, повреждённый ввод).

Вот конкретный пример для фичи вроде reserveInventory(itemId, qty):

Контракт может говорить, что qty должен быть положительным целым, функция должна быть атомарной и никогда не создавать отрицательный запас. Это сразу предлагает высокосигнальные тесты: qty = 0, qty = 1, qty больше доступного, конкурентные вызовы и принудительная ошибка базы данных на середине операции.

Если вы используете инструмент вроде Koder.ai, тот же рабочий процесс применим: сначала пишите контракт в чате, а затем генерируйте тесты, которые прямо атакуют границы, режимы отказа и список «не должно случаться».

Паттерн запроса: чертёж для высокосигнальных тестов

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

You are helping me write HIGH-SIGNAL unit tests.

Context
- Language/framework: \u003cfill in\u003e
- Function/module under test: \u003cname + short description\u003e
- Inputs: \u003ctypes, ranges, constraints\u003e
- Outputs: \u003ctypes + meaning\u003e
- Side effects/external calls: \u003cdb, network, clock, randomness\u003e

Contract (keep it small)
1) Preconditions: \u003cwhat must be true\u003e
2) Postconditions: \u003cwhat must be true after\u003e
3) Error behavior: \u003chow failures are surfaced\u003e

Task
PHASE 1 (plan only, no code):
A) Propose 6-10 tests max. Do not include “happy path” unless it protects an invariant.
B) For each test, state: intent, setup, input, expected result, and WHY it is high-signal.
C) Invariants: list 3-5 invariants and how each will be asserted.
D) Boundary matrix: propose a small matrix of boundary values (min/max/empty/null/off-by-one/too-long/invalid enum).
E) Failure modes: list negative tests that prove safe behavior (no crash, no partial write, clear error).
Stop after PHASE 1 and ask for approval.

PHASE 2 (after approval):
Generate the actual test code with clear names and minimal mocks.

Практический трюк — требовать матрицу границ компактной таблицей, чтобы пробелы были очевидны:

DimensionValid edgeJust outside“Weird” valueExpected behavior
length0-110,000error vs clamp vs accept

Если Claude предлагает 20 тестов, надавите на слияние похожих случаев и оставьте только те, которые действительно поймают баг (off-by-one, неверный тип ошибки, молчаливая потеря данных, сломанный инвариант).

Шаг за шагом: запустите запрос и превратите вывод в тесты

Экспериментируйте, не ломая main
Итеративно дорабатывайте наборы тестов безопасно — снимки и откат при появлении шума.

Начните с маленького, конкретного контракта для поведения, которое хотите протестировать. Вставьте сигнатуру функции, короткое описание входов и выходов и существующие тесты (даже если это только happy-path). Это удерживает модель на том, что код действительно делает, а не на её догадках.

Далее потребуйте таблицу рисков перед тем, как просить код тестов. Потребуйте три колонки: пограничные случаи (границы допустимого ввода), режимы отказа (плохой ввод, отсутствие данных, таймауты) и инварианты (правила, которые всегда должны быть верны). Добавьте одну фразу на строку: “почему это может сломаться.” Простая таблица выявляет пробелы быстрее, чем гора файлов тестов.

Затем выберите минимальный набор тестов, где каждый имеет уникальную цель по нахождению бага. Если два теста падают по одной и той же причине, оставьте более сильный.

Практическое правило отбора:

  • Оставляйте тесты, которые попадают в разные границы (min, max, empty, off-by-one).
  • Оставляйте тесты, которые доказывают безопасное поведение при отказе (понятная ошибка, нет частичных записей, нет краха).
  • Оставляйте тесты, которые утверждают инвариант (порядок, итоги, идемпотентность, отсутствие дубликатов).
  • Урезайте тесты, которые просто повторяют «работает с нормальным вводом».

Наконец, требуйте по короткому объяснению на тест: какой баг он бы поймал, если бы упал. Если объяснение расплывчато («валидирует поведение»), тест, скорее всего, низкоценностный.

Как закодировать инварианты в утверждения

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

Выберите 1–2 инварианта, которые реально вас защищают от багов. Хорошие инварианты часто про безопасность (нет потери данных), согласованность (одинаковые входы → одинаковые выходы) или лимиты (никогда не превышать caps).

Превращаем инвариант в проверяемое утверждение

Запишите инвариант коротким предложением, затем решите, какие доказательства доступны в тесте: возвращаемые значения, сохранённые данные, эмитируемые события или вызовы зависимостей. Сильные утверждения проверяют и результат, и побочные эффекты, потому что многие баги скрываются в «вернул OK, но записал не то».

Например, у вас есть функция, применяющая купон к заказу:

  • Инвариант: итог не может быть отрицательным.
  • Инвариант: повторное применение того же купона не должно давать двойной скидки.

Теперь закодируйте их как конкретные утверждения:

expect(result.total).toBeGreaterThanOrEqual(0)
expect(db.getOrder(orderId).discountCents).toBe(originalDiscountCents)

Избегайте расплывчатых утверждений типа «возвращает ожидаемый результат». Утверждайте конкретное правило (неотрицательность) и конкретный побочный эффект (скидка применена один раз).

Добавьте заметку с контрпримером, чтобы тест оставался острым

Для каждого инварианта добавьте короткую заметку в тест о том, какие данные бы его нарушили. Это помогает тесту не превратиться позже в проверку happy-path.

Простой шаблон, который долго держится:

  • Поместите инвариант в имя теста.
  • Утвердите инвариант по выходу.
  • Утвердите ключевой побочный эффект (или его отсутствие).
  • Добавьте комментарий с примером нарушающего случая (например, очень большой купон или повторное применение).

Режимы отказа: пишите тесты, которые доказывают безопасное поведение

Высокосигнальные тесты часто подтверждают, что код корректно падает. Если модель пишет лишь happy-path тесты, вы узнаете мало о том, как фича ведёт себя при грязных входах и проблемах зависимостей.

Начните с определения, что значит «безопасно» для этой фичи. Возвращаете ли вы типизированную ошибку? Переходите к значению по умолчанию? Пытайтесь ли повторить один раз, а затем остановиться? Запишите это в одном предложении и заставьте тесты это доказывать.

Когда просите Claude Code генерацию тестов режимов отказа, делайте цель строгой: покрывайте способы, которыми система может сломаться, и утверждайте точный ожидаемый ответ. Полезная фраза: «Предпочитайте меньше тестов с сильными утверждениями, а не много поверхностных».

Категории отказов, которые дают лучшие тесты:

  • Плохие входы: некорректный формат, отсутствие обязательных полей, значения вне диапазона
  • Отказы зависимостей: тайм‑ауты, 500-е, пустые ответы, повреждённые полезные нагрузки
  • Проблемы порядка: события в неверном порядке, дубликаты, частичные записи
  • Конкуренция: гонки обновлений, проверки идемпотентности
  • Поведение восстановления: когда вернуть ошибку vs откатиться vs повторить

Пример: есть endpoint, который создаёт пользователя и вызывает почтовый сервис для приветственного письма. Низкоценностный тест проверит «возвращает 201». Высокосигнальный тест отказа проверит, что если сервис отправки почты тайм‑аутит, вы либо (a) всё ещё создаёте пользователя и возвращаете 201 с флагом "email_pending", либо (b) возвращаете понятный 503 и не создаёте пользователя. Выберите одно поведение и утверждайте и ответ, и побочные эффекты.

Также тестируйте, что вы не утекали данные. При ошибке валидации убедитесь, что в БД ничего не записано. При получении повреждённого payload от зависимости — убедитесь, что вы не бросаете непойманные исключения и не возвращаете сырые стектрейсы.

Частые ловушки, которые порождают низкоценностные тесты

Примените шаблон на практике
Используйте Koder.ai, чтобы превратить шаблон-промпт в повторяемые и обозримые практики тестирования.

Низкоценностные наборы тестов обычно возникают, когда модель «вознаграждается» за объём. Если ваш запрос просит «20 unit тестов», вы часто получаете мелкие вариации, которые выглядят тщательными, но не ловят ничего нового.

Частые ловушки:

  • Тесты-похожести: один и тот же «валидный ввод» повторяется с разными строками или числами.
  • Тесты, которые зеркалят код: утверждают приватные шаги или вызовы хелперов вместо наблюдаемого поведения.
  • Мокирование всего подряд: подменяете БД, часы, сеть и конфиг одновременно.
  • Слабые утверждения: только «без ошибки», «не null» или «status is 200».
  • Грязное глобальное состояние: оставленные seed-данные, изменённые глобалы или кэшированные значения.

Пример: функция "create user": десять happy-path тестов могут варьировать email и всё равно пропустить важные вещи: отклонение дубликатов, пустой пароль и гарантию, что возвращаемые user ID уникальны и стабильны.

Правила-ограничители, полезные при ревью:

  • Требуйте, чтобы каждый тест называл риск, который он покрывает (граница, режим отказа или инвариант).
  • Избегайте проверок только реализации, если они не влияяют на наблюдаемое поведение.
  • Минимизируйте мокирование и допускайте несколько тестов, которые проверяют реальную интеграцию, когда это возможно.
  • Потребуйте сильные утверждения: точные выходы, изменения состояния и тип/сообщение ошибки.
  • Добавьте правила очистки, чтобы тесты не зависели от порядка выполнения.

Пример: как превратить одну фичу в небольшой сильный набор тестов

Представьте фичу: применение купона на кассе.

Контракт (маленький и тестируемый): принимая subtotal в центах и опциональный купон, вернуть итог в центах. Правила: процентные купоны округляют вниз до цента, фиксированные купоны вычитают фиксированную сумму, и итог никогда не меньше 0. Купон может быть недействительным, просроченным или уже использован.

Не просите «тесты для applyCoupon()». Просите тестирование пограничных случаев, режимов отказа и инвариантов, привязанных к этому контракту.

Границы, чтобы заставить поведение проявить себя

Выбирайте входы, которые ломают арифметику или валидацию: пустая строка купона, subtotal = 0, subtotal чуть ниже и выше минимума для скидки, фиксированная скидка больше subtotal и процент вроде 33%, который создаёт вопросы округления.

Режимы отказа, чтобы доказать безопасное поведение

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

Минимальный высокосигнальный набор тестов (5 тестов) и что каждый ловит:

  • Отклонение пустого или состоящего только из пробелов кода: ловит «принятие пустого как валидного» и ошибки триминга.
  • Округление процентного купона (subtotal 101, 33%): ловит ошибки округления и off-by-one центов.
  • Фиксированная скидка больше subtotаla (subtotal 500, скидка 1000): доказывает инвариант «итог не отрицателен».
  • Граница минимальной суммы (subtotal 999 vs 1000): ловит неверную логическую проверку (< vs <=).
  • Ошибка lookup купона или тайм‑аут: доказывает безопасное поведение (скидка не применяется) и стабильную обработку ошибок.

Если эти тесты проходят, вы покрыли типичные точки поломки без набивания набора тестов дубликатами happy-path.

Быстрый чеклист для высокосигнальных автогенерируемых тестов

Получите меньше, но сильнее тестов
Запросите 6–10 тестов, которые нацелены на границы, режимы отказа и инварианты.

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

Используйте этот чеклист как ворота:

  • Границы по каждому входу: для каждой поля (строки, ID, временные метки, флаги) включите хотя бы один краевой случай (пусто vs только пробелы, макс длина, ноль vs отрицательное, отсутствие необязательных полей, один за пределом).
  • Отказы зависимостей: включите хотя бы один тест, где зависимость ведёт себя неправильно (тайм‑аут БД, API 500, просроченный токен). Докажите безопасное поведение (понятная ошибка, нет частичной записи).
  • Инварианты с сильными утверждениями: выберите 1–3 правила, которые всегда должны выполняться, и утверждайте их напрямую. Избегайте расплывчатых «response is ok».
  • По одному уникальному багу на тест: прочтите заголовок теста и спросите «какую конкретную ошибку поймает этот тест?». Если два теста отвечают на один вопрос — объедините.
  • Тест удаления: попробуйте удалить тест. Если ничего значимого не потеряется (нет границы, нет режима отказа, нет инварианта), значит тест не заслуживает места.

Практический приём после генерации: переименуйте тесты в формат «should <поведение> when <условие>» и «should not <плохой исход> when <отказ>». Если переименование не удаётся сделать чётко, тест слабо сфокусирован.

Если вы используете Koder.ai, этот цикл хорошо ложится на снимки и откат: сгенерировали тесты, запустили, при необходимости откатили, пока высокосигнальный набор не устоится.

Следующие шаги: сделайте это повторяемым процессом

Относитесь к вашему промпту как к повторяемому хаберу, а не как к одноразовому запросу. Сохраните один шаблон (тот, который заставляет проверять границы, режимы отказа и инварианты) и используйте его для каждой новой функции, endpoint-а или UI‑потока.

Простая привычка, которая быстро улучшает результаты: требуйте по одному предложению на тест, объясняющему, какой баг он поймает. Если это предложение общее, тест — шум.

Ведите живой список доменных инвариантов для вашего продукта. Не храните его в голове. Добавляйте туда каждый раз, когда находите реальную ошибку.

Лёгкий рабочий цикл, который можно повторять:

  • Извлеките маленький контракт: входы, выходы, обработка ошибок и 3–5 инвариантов.
  • Запустите шаблонный промпт и потребуйте границы, режимы отказа, инварианты и однострочные обоснования.
  • Реализуйте только топ‑5–10 тестов, покрывающих разные риски.
  • Рефакторьте, затем снова запустите промпт, чтобы увидеть, какие новые риски появились.
  • Обрежьте дубликаты и оставьте тесты, которые бы поймали прошлые инциденты.

Если вы создаёте приложения через чат, запускайте этот цикл внутри Koder.ai (koder.ai) — так контракт, план и сгенерированные тесты останутся в одном месте. Когда рефактор неожиданно изменит поведение, снимки и откат помогут сравнить и довести набор высокосигнальных тестов до стабильности.

FAQ

Сколько модульных тестов генерировать на функцию?

По умолчанию: стремитесь к небольшому набору тестов, которые действительно поймали бы реальную ошибку.

Практичный предел — 6–10 тестов на единицу (функция/модуль). Если нужно больше, обычно это признак того, что единица делает слишком многое или контракт неясен.

В чём проблема генерации множества happy-path тестов?

Тесты «по счастливому пути» в основном доказывают, что пример по-прежнему работает. Они часто упускают вещи, которые ломаются в продакшене.

Высокосигнальные тесты нацелены на:

  • Пограничные случаи (0/1/максимум, пусто/null, off-by-one)
  • Режимы отказа (тайм-ауты, некорректные входы, ошибки зависимостей)
  • Инварианты (правила, которые всегда должны выполняться, например «нет частичной записи при ошибке»)
Что стоит записать перед запросом у ИИ генерации тестов?

Начните с краткого контракта, который можно прочесть в один вдох:

  • Inputs: типы, допустимые диапазоны, что считается пустым/отсутствующим
  • Outputs: форма успеха и форма ошибки
  • Side effects: что может быть записано/изменено (БД, файлы, сеть)
  • “Никогда не должно происходить”: сбой, молчаливая потеря данных, двойное списание, частичные записи

Затем генерируйте тесты из этого контракта, а не только из примеров.

Какие пограничные случаи обычно стоит тестировать?

Сначала протестируйте эти случаи:

  • Мин/макс значения (0, 1, max, max+1)
  • Пусто vs присутствует ("", [], null)
  • Off-by-one (n-1, n, n+1)
  • Грани форматирования (строки только из пробелов, ведущие нули)
  • Временные отсечки (незадолго до/после истечения)

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

Как написать тест на режим отказа, а не поверхностный?

Хороший тест на режим отказа доказывает два момента:

  1. Функция возвращает понятную ожидаемую ошибку (тип/сообщение/статус).
  2. Она ведёт себя безопасно:
  • нет частичных изменений состояния
  • нет утечек внутренних деталей
  • нет ненужных повторных попыток или побочных эффектов

Если involved запись в БД — всегда проверьте, что произошло в хранилище после ошибки.

Как превратить инвариант в утверждение теста?

Стандартный подход: превратите инвариант в утверждение по наблюдаемым результатам.

Примеры:

  • “Итог никогда не отрицательный” → expect(total).toBeGreaterThanOrEqual(0)
  • “При ошибке нет изменений состояния” → проверьте отсутствие новых строк / отсутствие переключенных флагов
  • “Идемпотентность” → вызовите дважды и убедитесь, что второй вызов не меняет состояние

Проверяйте и возврат значения, и побочные эффекты — многие баги скрываются в «вернул OK, но записал неправильно».

Когда тест обычного пути всё ещё стоит оставлять?

Стоит оставить happy-path тест, когда он защищает инвариант или критическую интеграцию.

Хорошие причины оставить его:

  • Утверждает ключевой инвариант для нормального ввода (например правила округления)
  • Подтверждает контракт API, на который полагаются вызывающие
  • Защищает от регрессии, связанной с прошлой проблемой

В противном случае замените его на пограничные/отказные тесты, которые ловят больше классов ошибок.

Что просить у модели до генерации тестового кода?

На этапе PHASE 1 требуйте только план.

Попросите модель предоставить:

  • 6–10 предложенных тестов максимум
  • Для каждого: цель, настройка, вход, ожидаемый результат и почему это высокосигнально
  • Небольшую матрицу границ
  • Список режимов отказа
  • 3–5 инвариантов и как их проверять

Только после утверждения плана — генерируйте код. Это предотвращает «20 похожих тестов».

Как избежать хрупкости тестов из‑за чрезмерного мокирования?

Правило по умолчанию: мокайте только ту границу, которая вам не принадлежит (БД/сеть/часы), и держите всё остальное реальным.

Чтобы избежать лишнего мока:

  • Не мокайте внутренние хелперы ради проверки реализации
  • Используйте реальную in-memory версию или простой фейк с предсказуемым поведением
  • Мокайте часы/рандом только когда это влияет на утверждение

Если тест «ломается при рефакторе, но поведение не поменялось», значит он слишком связан с реализацией или перетестирован.

Как быстро понять, низкоценностный ли сгенерированный тест?

Простой «тест удаления» поможет понять ценность теста:

  • Если удалив тест вы не потеряли ни одной границы, ни одного режима отказа и ни одного инварианта, то он не заслуживает места.

Проверяйте дубликаты:

  • Если два теста падают из‑за одной и той же ошибки, оставьте тот, у которого сильнее утверждение.
  • Если утверждения только «не null» или «status 200», усильте или удалите тест.

Похожие статьи