Автоматизация тестирования: промт для генерации unit-тестов

Есть ловушка, в которую попадают почти все, кто начинает генерировать тесты. Модель смотрит на код и пишет тесты по нему. Получается зеркало: тесты повторяют логику реализации, включая её ошибки, и зелёные всегда. Формально покрытие выросло, фактически защиты нет.
Лечится сменой источника. Тесты пишутся не по коду, а по ожидаемому поведению. Значит, и в промт надо давать не реализацию, а контракт.
Промт по контракту, а не по коду
Ты пишешь тесты. Вот описание функции — реализацию я намеренно не показываю. СИГНАТУРА: [имя, параметры с типами, возвращаемое значение] ЧТО ДЕЛАЕТ: [в двух-трёх фразах, на языке предметной области] ПРЕДУСЛОВИЯ: [что гарантирует вызывающий] ПОСТУСЛОВИЯ: [что гарантирует функция] ОШИБКИ: [какие исключения и когда] Напиши тесты на [pytest / unittest], покрывающие: 1. Обычный случай — 2 теста с разными данными. 2. Границы: пустое, один элемент, максимум, ноль, отрицательное. 3. Некорректный вход: каждый описанный класс ошибок отдельным тестом. 4. Инварианты: что должно оставаться верным при любых входных данных. ПРАВИЛА: - Одна проверка на тест. Имя теста описывает сценарий, а не функцию. - Никаких моков там, где можно обойтись реальными объектами. - Не подстраивай ожидаемые значения под удобство — считай их по описанию. - Отдельно перечисли случаи, которые ты не смог протестировать, и почему.
Скрыть реализацию — самый действенный приём во всём этом тексте. Тесты, написанные вслепую по контракту, регулярно вскрывают расхождение между тем, что код делает, и тем, что от него ждали.
Проверка самих тестов
Тест бесполезен, если проходит и на сломанном коде. Проверяется это просто: сломайте код нарочно.
Вот функция и тесты к ней. Предложи 7 мелких изменений в функции, которые её ломают: сдвиг границы на единицу, замена < на <=, перестановка аргументов, пропуск проверки, возврат вместо исключения, неверный порядок операций, отсутствие обработки пустого входа. Для каждого скажи, упадёт ли хоть один тест. Изменения, которые тесты не заметят, — это дыры. Перечисли их отдельно.
Это ручная версия мутационного тестирования, и она честнее любого процента покрытия. Покрытие показывает, какие строки выполнились, а не какие ошибки поймаются.
Что стоит и не стоит тестировать
| Стоит | Не стоит |
|---|---|
| Логику с ветвлениями и вычислениями | Геттеры, сеттеры и обёртки в одну строку |
| Границы диапазонов и разбор форматов | Работу сторонней библиотеки |
| Обработку ошибок и откаты | Приватные детали реализации |
| Места, где уже находили баги | Код, который вы завтра удалите |
Отдельный приём: тест из бага
Каждый найденный баг — готовое техзадание на тест. Промт короткий и окупается быстрее всех остальных:
Вот описание бага: [что сделали, что ожидали, что получилось]. Вот исправление: [диф]. Напиши тест, который падал бы до исправления и проходит после. Тест должен проверять поведение, а не конкретные строки исправления — чтобы он остался осмысленным после рефакторинга.
Чего не надо ждать
- Интеграционных сценариев. Модель не знает, как ваши части соединяются в реальности.
- Реалистичных данных. Она сгенерирует «test@example.com» и «Иван Иванов», а падает всё обычно на данных с пробелами, эмодзи и апострофами в фамилии.
- Понимания, что важно. Она распределит внимание равномерно по всем функциям, включая те, что никогда не ломались.
- Правильных ожидаемых значений в сложных расчётах. Их считайте сами: если модель ошиблась в ожидании, тест закрепит ошибку.
Разумный режим работы
Тесты на новую логику — писать самому, хотя бы каркас: пока формулируешь, что проверять, обычно и находишь дыры в замысле. А вот дополнить набор скучными краевыми случаями, размножить параметризацию, написать тест на найденный баг — тут генерация экономит реальное время и не портит качество.