PPПётр ПашкуровCursor · наставничество · заказы

Руководство · Cursor · Тесты

Как проверить изменения от ИИ: минимальный набор тестов

После diff Cursor Agent зафиксируйте критерий готовности - какой сценарий не должен сломаться. Локально: lint/typecheck, smoke или тесты затронутого модуля, для крупных правок - маркер regression. Полный suite - в CI на PR. Зелёный прогон агента - черновик; merge PASS остаётся у вас после review.

Как проверить изменения от ИИ: минимальный набор тестов

Как устроен тестовый контур после правок ИИ в Cursor

Agent в Cursor умеет менять десятки файлов и сам запускать terminal (overview). Это не отменяет вашу ответственность за merge: в best practices прямо сказано, что review обязателен, а зелёный прогон внутри сессии - не финальный gate. Автотесты здесь не про «покрыть всё на 90%», а про обратимость: вы знаете, какой сценарий сломался, до деплоя.

На наставничестве я начинаю не с pytest, а с критерия готовности. Пример из недавнего разбора: Telegram-бот принимает заявку с лендинга, пишет в CRM без дублей, отвечает пользователю за три секунды. Пока этот путь не проверен - merge рано, даже если агент уверенно отчитался о lint. Тестовый контур - мост между diff и продом.

Cloud Agent и песочницы вроде LangChain sandbox закрывают другой разрыв: у агента есть VM, где он может собрать проект и прогнать тесты до PR (cloud agent). Но без вашего минимального набора локально цикл не замыкается: среда агента не равна вашему staging, а hooks «пока тесты не зелёные» работают только если тесты вообще есть в репо.

Три слоя, которые я держу в голове после каждого Apply All:

  1. 01

    Быстрый локальный прогон - lint, typecheck, targeted test на затронутый модуль.

  2. 02

    Регрессия затронутого сценария - не весь репозиторий, а путь, который правили.

  3. 03

    Полный suite на PR в CI - pull_request trigger (GitHub Actions).

Если тестов в проекте нет совсем - честный минимум: один smoke-скрипт на критический путь плюс ручная проверка формы. Написание автотестов с нуля - отдельная задача; здесь - что гонять уже сегодня, когда Agent только что переписал handler.

Матрица «тип diff - минимальный прогон» экономит вечер:

Тип diffМинимум до mergeРегрессия
Косметика / copy / CSSlint + ручной smoke страницыполный suite не нужен
Одна функция / модульpytest path/to/test_*.py или npm test -- fileтесты модуля + 1 критический E2E/smoke
API-контракт / сигнатураtypecheck + тесты вызывающих местмаркер regression / affected paths
Зависимости / конфиг / CIlockfile diff + npm ci / buildполный CI на PR
Миграция БД / платёж / authинтеграционный сценарий + откатполный регресс + staging

Перед первым промптом в новом репо полезно открыть первый проект в Cursor - там вечерний цикл Plan → Agent → verify. Здесь углубляем только тестовую часть verify.

Схема: тестовый контур от diff к локальной проверке и CI на PR

Как покрыть unit-тестами первый контур

Unit тесты проверяют один кусок логики изолированно: функцию расчёта скидки, парсер телефона, валидацию email. Юнит тесты не ловят «кнопка на лендинге не кликается» - для этого smoke или E2E. После правок ИИ я смотрю, какие функции затронул diff, и гоняю тесты именно этого модуля, а не весь пакет.

Приоритет покрытия на разборах бизнес-репо:

  1. 01

    Деньги и доступ: расчёт цены, лимиты, проверка роли, токен webhook.

  2. 02

    Данные клиента: нормализация телефона, дедупликация заявки, маппинг полей в CRM.

  3. 03

    Контракты между модулями: что API handler ожидает на входе и отдаёт наружу.

  4. 04

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

  5. 05

    Ошибки с понятным кодом: 400 vs 500, чтобы прод не глотал сбой молча.

Типичная картина: агент переписал normalize_phone() и добавил тест только на +7999.... Старый кейс с 8 (999) падает в проде. Я прошу в промпте: «добавь unit тесты на все форматы из README и на пустой ввод». Agent иногда делает это хорошо - но прогон остаётся у вас.

Для JavaScript тот же принцип: npm test -- src/utils/phone.test.ts бьёт в один файл, не ждёт всего Jest suite. Для Python - pytest tests/test_phone.py -k normalize. Не гоняйте триста тестов после правки одной строки в CSS - это другая строка матрицы выше.

Когда unit тестов мало, я добавляю один «якорный» тест на бизнес-правило, которое уже ломалось. Не ради метрик покрытия - ради сигнала при следующем diff. Один тест на «заявка не уходит дважды при двойном клике» полезнее десяти на геттеры.

Связка с Cursor: в rules можно прописать «после изменения src/billing/ запускай npm test -- billing». Agent иногда слушается, иногда нет - terminal-команда в чате не заменяет ваш локальный прогон перед commit.

Матрица приоритетов unit-тестов: деньги, данные клиента, контракты API

Как прогнать регрессию после каждого diff

Регрессионное тестирование отвечает на вопрос: старое поведение живо после новой правки? Регрессионные тесты - не отдельная религия, а набор сценариев, которые уже однажды спасли от бага. После diff от Agent я не запускаю «всё подряд», а выбираю слой по ширине изменений из таблицы в первом разделе.

Узкий diff в одном модуле: тесты модуля плюс один критический smoke. Пример - правка handler Telegram-бота: pytest tests/test_bot_handlers.py и ручная отправка тестовой заявки на staging-бота. Широкий diff в package.json или миграции БД: полный CI на PR, staging, план отката.

В pytest маркеры smoke и regression помогают разделить скорость и глубину (pytest mark):

# быстрый контур после мелкой правки
pytest -m smoke

# перед merge крупной фичи
pytest -m regression

Маркеры нужно один раз описать в pytest.ini или pyproject.toml - иначе -m smoke ничего не найдёт. Agent часто добавляет тесты без маркеров; тогда targeted path надёжнее абстрактного -m.

Регрессия для API-контракта: если агент поменял сигнатуру createLead(payload), гоняю тесты всех мест, которые импортируют эту функцию, плюс typecheck (tsc --noEmit или mypy). Сломать вызывающий код - частый побочный эффект «улучшения» от модели.

На разборе лендинга с формой я фиксирую регрессионный сценарий текстом: «отправка с валидным телефоном → 200 → запись в таблице → письмо в Telegram». Пока цепочка не зелёная - не merge. Это и есть регрессионное тестирование без фанатизма: один живой путь, который приносит деньги.

CI на pull_request - место для полного suite. Локально - targeted. Если гонять всё на ноутбуке после каждого Apply All, устанете и начнёте пропускать verify - я это вижу у соло-основателей на третьей неделе с Agent.

Чеклист регрессионного тестирования: smoke, targeted path и полный CI на PR

Как запустить автотесты на Python через pytest

pytest - мой дефолт для автотестов на python в бизнес-скриптах, ботах и небольших API. Соглашение простое: файлы test_*.py, проверки через assert, запуск одной командой (getting started). Не tutorial TDD с нуля - быстрый старт, чтобы после Cursor было что нажать.

Минимальный тест после правки модуля:

# tests/test_lead_normalize.py
from app.leads import normalize_phone

def test_normalize_strips_spaces():
    assert normalize_phone("8 (999) 123-45-67") == "+79991234567"

def test_normalize_rejects_empty():
    assert normalize_phone("") is None

Запуск только этого файла - не весь репозиторий:

pytest tests/test_lead_normalize.py -q

Для smoke-набора после мелкого diff:

pytest -m smoke --maxfail=1 -q

Флаги -q и --maxfail=1 экономят время: первый красный тест останавливает прогон, вы сразу видите причину в логе для промпта Agent.

pytest тесты агент часто пишет прилично, но любит захардкодить «счастливый» API-ключ в фикстуре. Перед commit проверяю diff на SECRET, TOKEN, реальные URL продакшена. Это пересекается с проверкой кода от ИИ, но повторю: тестовый файл тоже попадает в git.

Если проект на Node - та же логика, другая команда: npm test -- src/leads/phone.test.ts. Написание автотестов на python или JS следует одной матрице из первого раздела; меняется только runner.

Структура папки, которую я рекомендую на разборах:

ПутьНазначение
tests/unit/чистая логика, быстрый прогон
tests/integration/БД, внешние API, staging credentials
pytest.iniмаркеры smoke, regression

Agent без явного промпта кладёт всё в tests/test_foo.py - нормально для старта. Маркеры и split на unit/integration можно сделать отдельной задачей, когда контур уже спасает от регресса.

Минимальный чеклист перед merge и деплоем

Чеклист - это не бюрократия, а пять минут, которые отделяют «агент сказал готово» от «форма в проде реально шлёт заявку». Я прохожу его сам после каждого крупного Apply All и даю ученикам как скрипт в terminal history.

Пять шагов после Apply All:

# 1. lint / format
npm run lint          # или ruff check . / eslint по проекту

# 2. targeted test на затронутый модуль
pytest path/to/test_module.py -q
# JS: npm test -- path/to/file.test.ts

# 3. regression marker, если diff шире одного файла
pytest -m regression --maxfail=3 -q

# 4. push ветки - CI на pull_request
git push -u origin HEAD

# 5. smoke критического пути (ручной или скрипт)
# пример: curl staging /health и тестовая заявка в форму

Шаг 4 не заменяет шаги 1-3. CI ловит то, что вы запушили; если локально не гоняли targeted test, в PR уедет красный build и вы потеряете время на цикл «push - ждать - чинить».

Для предпринимателя без dev-фона критерий готовности удобнее держать в JSON рядом с задачей - не в pytest, а как контракт «что значит готово»:

{
  "feature": "lead_form_to_telegram",
  "ready_when": [
    "форма на /contact принимает +7 и 8",
    "дубликат за 60 сек не создаёт вторую запись в CRM",
    "сообщение приходит в Telegram-чат менеджеров",
    "пользователь видит экран «спасибо», не 500"
  ],
  "smoke_command": "npm run test:smoke -- contact-form",
  "owner_review": true
}

Такой файл можно положить в docs/acceptance/ или в корень как acceptance-lead-form.json. Agent читает его через @acceptance-lead-form.json в промпте - и вы сверяете прогон с пунктами ready_when, а не с абстрактным «всё работает».

Таблица «когда какой шаг обязателен»:

СитуацияШаги 1-2Шаг 3 regressionШаг 4 CIРучной smoke
Правка текста на лендингеlintнетпо желаниюда, одна страница
Новый handler в ботедададатестовое сообщение боту
Обновление зависимостейbuildполный CIдакритический путь
Платёж / authинтеграционныеполныйда + stagingсценарий отката

Зелёный прогон Agent в чате не ставлю галочку в чеклисте - только вывод команд, которые я сам скопировал в terminal и увидел exit code 0. Как проверить код ии агента на практике - воспроизвести команды у себя, а не скриншот из сессии.

Перед merge сверяюсь с Git и Cursor: ветка не main, diff просмотрен, commit после зелёного verify. Тесты - следующий слой той же дисциплины.

Что ломается: типичные ошибки автотестов и как их чинить

Здесь не общие советы «пишите больше тестов», а симптомы с фиксом, которые я ловлю на разборах после Cursor.

Flaky: тест то зелёный, то красный

Симптом: pytest падает на таймере, sleep или сравнении datetime.now(). Агент любит time.sleep(1) вместо мока часов. Фикс: freezegun или dependency injection времени; для async - явный await с таймаутом в тесте, не в прод-коде. Локально гоняю три раза подряд: pytest tests/test_x.py --count=3 (если установлен pytest-repeat) или простой цикл в shell. Если хоть раз красный - не merge.

Ложнозелёный: тест проходит, прод падает

Симптом: assert слишком слабый - assert result is not None вместо проверки значения. Или тест бьёт в мок, а прод ходит в реальный API. Фикс: assert на конкретное поведение из ready_when; один интеграционный тест без мока на staging credentials. Ещё вариант - тест импортирует старую функцию из кэша после rename: pytest --cache-clear и проверка, что путь импорта в тесте совпадает с diff.

Импорты и окружение: ModuleNotFoundError после Agent

Симптом: агент добавил tests/test_foo.py, но package не в PYTHONPATH, или виртуальное окружение в terminal Cursor не то. Фикс: запуск из корня репо с активированным venv; в pyproject.toml или setup.cfg проверить pythonpath = .. Для monorepo - pytest apps/bot/tests с -c apps/bot/pytest.ini. В промпте: «запусти pytest из корня и приложи полный traceback» - и чините по логу, не по словам «у меня прошло».

Дополнительные частые сбои:

СимптомВероятная причинаФикс
Все тесты skippedневерный маркер -mpytest --collect-only
CI красный, локально зелёныйдругая версия Node/Pythonсверить .nvmrc / python-version в Actions
Тест падает только в CIнет env varsecrets в GitHub, не в тесте
Agent «починил» тест удалением assertослабление проверкиdiff теста обязателен в review

Ошибка, которую видел чаще всего: ученик merge после того, как Agent закомментировал падающий assert «временно». Временно - до инцидента в проде. Я возвращаю assert и чиню код, не тест.

Разобрать тестовый контур на вашем проекте

Если после прочтения остались вопросы «а у меня тестов нет» или «агент уже прогнал всё сам» - это нормальные возражения. На разборах я не продаю курс TDD: смотрим ваш репозиторий, фиксируем один критерий готовности, добавляем smoke или targeted pytest там, где боль уже была. Без магического процента покрытия и без обещания zero bugs.

Три типичных входа:

  1. 01

    Тестов нет - один smoke на заявку/оплату/логин плюс lint; дальше наращиваем по матрице diff.

  2. 02

    Тесты есть, но долго - маркеры smoke / regression, targeted path после Agent.

  3. 03

    Агент в Cloud - настраиваем минимальный прогон в VM, но merge PASS остаётся у вас после review ветки.

По теме в блоге: как проверять код от ИИ (diff, секреты, lint), Git и Cursor (ветка, откат), первый проект в Cursor (вечерний цикл с нуля).

FAQ

Частые вопросы

Нужны ли тесты, если код правит ИИ в Cursor?

Да. Agent пишет черновик и может запустить команды в terminal, но merge и ответственность за прод остаются у вас. Минимум после diff: lint, targeted test на затронутый модуль или smoke критического сценария. Полный suite - на PR в CI.

Сколько unit-тестов достаточно после правки агента?

Не привязывайтесь к проценту покрытия. Достаточно тестов на затронутую логику плюс один регрессионный сценарий, который уже ломался. После правки одной функции - файл тестов этого модуля; после смены API - тесты всех вызывающих мест.

Чем unit-тесты отличаются от регрессионных?

Unit проверяют один кусок логики изолированно - функцию, парсер, расчёт. Регрессионные отвечают на вопрос «старое поведение живо?» - цепочка заявка → CRM → Telegram или оплата → чек. После diff от Agent сначала unit затронутого модуля, при широких изменениях - маркер regression или полный CI.

Как проверить код перед выкладкой в прод?

Локально: lint, targeted pytest или npm test на изменённые файлы, ручной smoke критического пути по критерию готовности. Затем push ветки и зелёный CI на pull_request. Staging для платежей, auth и миграций БД. Зелёный отчёт агента в чате не заменяет ваш прогон команд в terminal.

pytest падает сразу после Apply All в Cursor - что делать?

Скопируйте полный traceback в промпт Agent, не merge. Частые причины: ModuleNotFoundError (неверный PYTHONPATH), сломанный импорт после rename, ослабленный assert, который агент «починил» комментарием. Запускайте из корня репо с активированным venv: pytest path/to/test_file.py -q.

Нужно ли гонять CI после каждого коммита агента?

Локально - targeted test после каждого зелёного шага, полный CI - при push ветки на pull_request. Не обязательно триггерить pipeline на каждый промежуточный commit в feature-ветке, если вы ещё в середине задачи. Перед merge в main - зелёный CI обязателен.

Как проверить код ИИ агента, если проект на JavaScript, а не Python?

Та же матрица diff → минимальный прогон. Вместо pytest: npm test -- path/to/file.test.ts, npm run lint, typecheck через tsc. Маркеры smoke и regression можно настроить в Jest или Vitest. Смысл не в языке, а в targeted прогоне затронутого модуля и CI на PR.

Дальше

Связанные страницы

Птенец в капюшоне печатает сообщение в телефоне
Telegram

Обсудить вашу задачу

Напишите в Telegram цель, что уже сделано и где стопор.

Отвечаю сам — без бота и «оставьте заявку».