Юнит-тесты на Python с LLM: 12 промтов от pytest-фикстур до Hypothesis, которые закрывают непокрытые ветки

Тесты редко пишут с удовольствием. Обычно их пишут на бегу, в конце спринта, когда менеджер уже спрашивает про релиз, — и именно поэтому они получаются хрупкими и бесполезными. LLM здесь работает не как «кнопка сделать тесты», а как напарник, который знает синтаксис pytest, помнит про pytest.mark.parametrize и не устаёт придумывать граничные случаи.

Ниже — 12 готовых промтов, которые можно копировать в ChatGPT, Claude или локальную модель. У каждого — задача, пример входных данных и пояснение, что именно вы получите на выходе. Проверяйте любой сгенерированный код: модель уверенно пишет тест, который зелёный, но проверяет не то, что вы думали.

Ключевое правило LLM хорошо пишет структуру теста и граничные случаи, но плохо знает вашу бизнес-логику. Чем больше контекста (сигнатуры, типы, поведение) вы даёте во входе, тем меньше правок после.

Генерация и параметризация

1. Базовый тест по сигнатуре функции

Задача: быстро получить скелет теста для чистой функции без побочных эффектов.

Промт:

Ты — Python-разработчик, пишущий тесты на pytest. Вот функция:

<код>
{вставь функцию}
</код>

Напиши тесты на pytest, которые покрывают:
1) типичный happy path,
2) граничные значения аргументов (пустая строка, 0, отрицательные числа, None, если допустимо),
3) ожидаемые исключения с pytest.raises и проверкой текста ошибки.
Не используй unittest. Каждый тест — отдельная функция с говорящим именем.
В конце — краткий список случаев, которые остались непокрытыми, и почему.

Пример: функция parse_price(text: str) -> Decimal, принимающая строки вида "1 234,50 ₽". Модель выдаст тест на обычную строку, на строку без копеек, на неразрывный пробел и на ValueError для "abc".

Последний пункт промта — самый ценный: список непокрытых случаев часто выявляет дыры в самом ТЗ, а не в коде.

2. Параметризованные тесты вместо копипасты

Задача: превратить пять почти одинаковых тестов в один parametrize.

Промт:

Сожми эти тесты в один параметризованный тест через @pytest.mark.parametrize.
Требования:
- каждый набор данных — отдельный элемент списка кортежей;
- добавь параметр ids с человекочитаемыми именами;
- если ожидается исключение, передавай его класс в параметре и проверяй через pytest.raises;
- сохрани все исходные проверки, ничего не выкидывай.

<код тестов>
{вставь тесты}
</код>

Зачем: параметризация — главный инструмент против «тестов-близнецов». Плюс при падении pytest покажет test_parse[negative], а не безымянный test_parse_3, и вы сразу поймёте, какой кейс сломался.

Фикстуры и изоляция

3. Фикстуры с правильной областью жизни

Задача: вынести подготовку окружения в фикстуры, не путая scope.

Промт:

У меня есть тесты, каждый из которых сам создаёт временную БД и поднимает подключение.
Вынеси подготовку в фикстуры pytest.
Правила:
- подключение к БД — scope="session";
- чистая схема на каждый тест — scope="function" с очисткой в yield-блоке;
- фикстуры положи в conftest.py;
- покажи, как тест запрашивает фикстуру по имени.
Объясни для каждой фикстуры, почему выбран именно такой scope и что сломается при другом.

Тонкость: scope="session" у подключения экономит секунды на старте, но если тест мутирует состояние и не убирает за собой, следующий тест упадёт «на пустом месте». Модель по умолчанию любит ставить function — и это правильный дефолт.

Что проверить первым Попросите модель добавить в фикстуру yield и очистку после него. Без teardown-а тесты пройдут поодиночке и развалятся при запуске всей папки — классическая ловушка.

4. Фабрика тестовых данных

Задача: избавиться от простыней User(name="...", email="...", created_at=...) в каждом тесте.

Промт:

Сделай pytest-фикстуру-фабрику для модели User (dataclass с полями name, email, created_at, is_active).
Требования:
- фикстура возвращает функцию make_user(**overrides);
- все поля имеют разумные дефолты, email уникален (uuid);
- overrides перекрывают любые поля;
- покажи три примера использования.

Выигрыш: тест читается как «сделай активного юзера с конкретным email», а не как «собери объект из восьми полей». Когда в модель добавят поле, править придётся одну фабрику, а не сорок тестов.

Мокирование

5. Мок внешнего HTTP API

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

Промт:

Функция get_rate() делает requests.get к внешнему API и парсит JSON.
Напиши тесты с моками через pytest + monkeypatch или unittest.mock.
Покрой:
1) успешный ответ 200 с валидным JSON;
2) 200, но в JSON нет нужного поля;
3) 404 и 500 — ожидаем наше кастомное исключение ApiError;
4) таймаут requests.Timeout;
5) проверку, что URL собран правильно (assert на аргумент вызова).
Покажи оба подхода: monkeypatch и MagicMock, объясни разницу.

Что важно: пункт 5 — assert на аргументы вызова — ловит баги, где код ходит не на тот endpoint. Без него мок «зелёный», а в проде 404.

6. Мок базы данных в сервисном слое

Задача: протестировать бизнес-логику без реальной БД.

Промт:

Сервис OrderService принимает репозиторий через конструктор (dependency injection).
Напиши тесты с фейковым репозиторием (не MagicMock, а класс FakeOrderRepo с dict внутри).
Покрой: создание заказа, отказ при недостатке товара, идемпотентность при повторном вызове с тем же idempotency_key.
Объясни, почему фейк лучше MagicMock именно здесь.

Ключевое различие: MagicMock подтвердит любой вызов и промолчит о неверной логике. Фейк с реальным словарём ведёт себя как настоящий репозиторий и падает там, где падал бы прод.

Property-based testing

7. Первый property-based тест на Hypothesis

Задача: найти входы, о которых вы не подумали.

Промт:

Напиши property-based тест на Hypothesis для функции encode/decode (base64-подобное кодирование).
Свойства, которые надо проверить:
1) decode(encode(x)) == x для любых bytes;
2) длина результата encode кратна 4;
3) encode не падает на пустой строке.
Используй @given и st.binary(). Покажи, как воспроизвести упавший пример через @example.

Почему это работает: Hypothesis не перебирает случаи из вашего списка, а генерирует их по стратегии. Он найдёт пустой ввод, строку из одних нулевых байтов и всё, что вы забыли. Минус — тест медленнее обычного и иногда падает на «странном» входе, который надо отдельно разобрать.

8. Сужение стратегий для домена

Задача: не генерировать бессмысленные данные вроде отрицательного возраста.

Промт:

У меня property-based тест для расчёта скидки: скидка(purchase_amount, user_age).
Ограничь стратегии Hypothesis: сумма от 0 до 1_000_000, возраст от 14 до 100.
Добавь инвариант: скидка никогда не больше 30% и не отрицательна.
Объясни, зачем фильтровать домен и что будет, если этого не делать (assume vs стратегии).

Неочевидный вывод: assume() отбрасывает неподходящие примеры уже после генерации, и при узком домене Hypothesis может сдаться с FailedHealthCheck. Правильнее сузить саму стратегию через st.integers(min_value=14, max_value=100).

Где чаще ошибаются Property-based тесты плохо ловят ошибки в формате вывода (например, «дата в ISO») — для этого нужны обычные примеры. Их сила в инвариантах: «что бы ни подали, результат не больше входа».

Покрытие и баг-репорты

9. Разбор отчёта coverage

Задача: найти ветки, которые не выполняются ни одним тестом.

Промт:

Вот вывод pytest --cov=app --cov-report=term-missing:
{вставь таблицу}

Для файла app/pricing.py найди непокрытые строки.
Предложи минимальный набор тестов, который закроет именно эти ветки, не раздувая набор.
Для каждой непокрытой строки объясни, какое условие её не достигает.

Тонкость: term-missing показывает номера строк, но не говорит, какое условие их пропускает. Модель по коду восстановит логику — и часто укажет на ветку if discount and user.is_vip, для которой нет теста с VIP-пользователем.

10. Тест из баг-репорта

Задача: превратить «упало у клиента» в воспроизводящий тест до фикса.

Промт:

Баг-репорт: «При оформлении заказа с промокодом на 100% скидки итоговая сумма становится отрицательной».
Вот код расчёта: {вставь функцию}.
Напиши failing-тест на pytest, который воспроизводит баг.
Тест должен падать на текущем коде и проходить после исправления.
Покажи ожидаемое поведение и текущее.

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

11. Проверка качества сгенерированных тестов

Задача: поймать тесты, которые зелёные, но бесполезные.

Промт:

Вот тест, сгенерированный ранее: {вставь тест}.
Оцени его критически:
- есть ли assert, который реально проверяет поведение, а не просто «не упало»;
- не тестирует ли он реализацию вместо контракта;
- не сломается ли он при безобидном рефакторинге;
- какие мутации кода он пропустит?
Дай вердикт: оставить, переписать, удалить.

Практика: «мутационное мышление» — самый быстрый способ отличить настоящий тест от декоративного. Если тест переживёт замену return x + 1 на return x + 2, он бесполезен.

12. Тест на асинхронный код

Задача: не забыть про pytest-asyncio и не получить «coroutine was never awaited».

Промт:

Напиши async-тесты на pytest для функции fetch_user(user_id), которая делает await к httpx-клиенту.
Используй pytest-asyncio, мок httpx через respx или AsyncMock.
Покрой: успех, 404, таймаут.
Добавь в пример конфигурацию asyncio_mode в pytest.ini.

Режим отказа: без asyncio_mode = auto в конфиге каждый async-тест нужно помечать @pytest.mark.asyncio, иначе pytest молча пропустит корутину. Это одна из самых частых причин «тестов нет, а покрытие есть».

Что в итоге

Промт Когда применять Риск
Базовый тест по сигнатуре новая чистая функция забудет про исключения
Параметризация много похожих тестов потеряет часть кейсов
Фикстуры со scope подготовка окружения неверный teardown
Фабрика данных модели с многими полями дефолты не отражают домен
Мок HTTP внешние API не проверит URL
Фейк-репозиторий сервисный слой сложнее в поддержке
Hypothesis инварианты и парсеры медленные прогоны
Разбор coverage закрытие веток тесты ради цифры
Тест из бага регрессии фиксирует неверное поведение

Главный trade-off всей затеи: LLM ускоряет написание тестов в разы, но не отвечает за их смысл. Быстрый черновик от модели плюс пять минут вашей проверки — это дешевле, чем час на рутину; сгенерированные тесты без ревью — дороже, чем их отсутствие, потому что создают иллюзию покрытия.

Начните с одного промта на функцию, которую давно хотели покрыть, и посмотрите, что вернёт модель. Если хотите смежный навык — разберите промты для код-ревью: тестирование и ревью отлично работают в паре. А для работы с данными пригодятся промты для PostgreSQL.

← Все статьи

Спросить AI по этой статье

Задайте вопрос по этой статье — ASI Biont ответит, опираясь на её текст.

Читайте также

ИИ для повседневной жизни: как построить реальный семейный бюджет с ChatGPT и Claude (без кода, 2026)

27 октября 2026

Как договориться с партнёром о быте: 5 правил коммуникации в паре без ссор

27 октября 2026

ChatGPT с телефона: как надиктовать и отправить жалобу в управляющую компанию за 5 минут

27 октября 2026

Как AI-двойник проверяет расчёт при увольнении по собственному желанию и находит недоплату

27 октября 2026

Цифровая гигиена за вечер: как найти утечки персональных данных и удалить информацию о себе из интернета

27 октября 2026

Сколько часов учить испанский до DELE 2026: разбор нормативов, статистики и гибридной подготовки с AI-репетитором

27 октября 2026

Расторжение договора с фитнес-клубом или салоном красоты и возврат денег: разбор условий, сроков и претензий в 2026 году

27 октября 2026

Cambridge Primary English (0058): почему начальные классы решают, будет ли ребёнок когда-либо читать бегло — и как на самом деле работает программа

27 октября 2026

Full-Stack AI Engineer: как RAG-пайплайны, векторные базы данных и развертывание LLM на самом деле работают в продакшене

28 октября 2026