Недавно писал, что очень неплохо получается с помощью ИИ делать юнит- и фича-тесты. А в этой заметке решил разобрать подробнее, на примере, как это выглядит.
Для определенности уточню, что использую я плагин Codeium для Visual Studio Code под Linux, тариф бесплатный. Никаких дополнительных настроек не применял, на сайте указано что плагином используются Chat GPT, Claude, DeepSeek и Gemini. Проект, с которым я работаю, написан на Laravel, в нём есть такая функция — на определенный route можно отправить post-запрос с сообщением, и это сообщение будет отправлено телеграм-ботом в соотвествующий чат.
Эта функция уже написана, на неё есть тесты, и она работает. А в данном примере мы создадим feature-тест, проверяющий ограничение на количество сообщений в день в соответствии с тарифным планом пользователя. Такого функционала в проекте еще нет, я его хочу создать в соответствии с методикой TDD. То есть сначала написав тест, а затем последовательно запуская его и исправляя появившиеся ошибки.
Вот для написания данного feature-теста я и применю ИИ. Открываем подходящий для теста файл, жмём Ctrl+I. Чат жопотэ, поехали!
Итак, пишем вот такой промпт*:
Create a test that checks that user cannot exceed the daily external notifications limit set in his plan. Expect it to be implemented as throttling.
что в переводе означает
Создай тест, проверяющий что пользователь не может превысить лимит на внешние уведомления, установленный в его плане. Ожидай, что функционал будет реализован через троттлинг.
*я обращаюсь к боту по-английски потому что мне так проще, я и комменты в коде всегда по-английски пишу. Но русский язык он тоже понимает, я проверял.
И вот тест, который немедленно сгенерировал мне Кодиум.
/** @test */
public function user_cannot_exceed_daily_external_notifications_limit() {
$user = User::factory()->create([]);
$plan = Plan::factory()->create([
'daily_notifications_limit' => 100,
]);
$user->plan()->associate($plan)->save();
$token = $user->createToken('api_token')->plainTextToken;
for ($i = 0; $i < 100; $i++) {
$response = $this->withHeaders([
'Accept' => 'application/json',
'Content-Type' => 'application/json',
'Authorization' => "Bearer $token",
])->json('POST', '/api/webhooks/arbitrary-notification', [
'type' => 'info',
'message' => 'Testing daily notifications limit.',
]);
$response->assertStatus(200); // successful request
}
$response = $this->withHeaders([
'Accept' => 'application/json',
'Content-Type' => 'application/json',
'Authorization' => "Bearer $token",
])->json('POST', '/api/webhooks/arbitrary-notification', [
'type' => 'info',
'message' => 'Testing daily notifications limit.',
]);
$response->assertStatus(429); // over the daily limit
$this->assertEquals(100, $response->headers->get('X-RateLimit-Limit'));
$this->assertEqualsWithDelta(86400, $response->headers->get('Retry-After'), 60); // 1 day in seconds
}
Комментарии в коде тоже кстати расставил ИИ. Теперь рассмотрим сгенерированный тест по кусочкам.
Название теста user_cannot_exceed_daily_external_notifications_limit
хорошее и лаконичное. Так бывает не всегда, иногда он половину промпта в него пихает.
$user = User::factory()->create([]);
$plan = Plan::factory()->create([
'daily_notifications_limit' => 100,
]);
$user->plan()->associate($plan)->save();
$token = $user->createToken('api_token')->plainTextToken;
Тут всё верно. ИИ создал пользователя, создал модель тарифного плана (она уже описана) и добавил в нее несуществующее пока поле с ограничением. Затем подцепил план к пользователю и создал токен доступа, которым мы авторизуем post-запросы с сообщениями.
Правда, ИИ забыл добавить самого бота с настройками, который собственно отправил бы сообщение. Без бота уведомление никто не отправит, а наш запрос вернет 404 с текстом о том, что бота нет. Так что можно сразу понять, чего не хватает, и исправить тест. Можно было бы конечно замокать интерфейс получения бота из юзера, но создать бота проще:
$bot = Bot::factory()->create(['user_id' => $user->id,]);
Причем я создание бота вписал вручную, но можно было попросить ИИ — и он бы добавил сам.
Что еще? Ну, перед циклом отправки запросов неплохо было бы замокать и саму отправку уведомлений, мы ведь не хотим, чтобы тест на самом деле в Телеграм долбился в данном случае. Но поскольку сама отправка существует давно, почти на всех тестах у нас стоит
$this->app->bind(TelegramBotNotificationInterface::class, FakeTelegramBotNotification::class);
то есть в зависимости вместо настоящего внедряется фейковый сервис, который ничего никуда не отправляет. Так что тут добавлять ничего не нужно. Хотя ИИ без указания и не добавил бы: откуда ему знать, что мы не хотим вызывать настоящий сервис. И, конечно, даже если запросы фейковые, незачем делать 100 и лишние доли секунды тратить на тест. Хватит и 20.
Далее ИИ написал нам цикл выполнения запросов вплоть до лимита, и затем проверку что 101-й запрос не выполнится:
$response->assertStatus(429); // over the daily limit
$this->assertEquals(100, $response->headers->get('X-RateLimit-Limit'));
$this->assertEqualsWithDelta(86400, $response->headers->get('Retry-After'), 60); // 1 day in seconds
эта проверка работает в точности так, как в Ларавеле реализуется троттлинг. Возврат ошибки 429 (Too Many Requests) с передачей значения лимита и времени когда лимит сбросится.
Что еще? Да, в общем-то, всё. Тест готов, можно писать ответную часть. Теперь если мы будем последовательно запускать тест и исправлять появившиеся ошибки, то
- Первая ошибка будет по отсутствию поля
daily_notifications_limit
в таблицеplans
, мы его добавим. - Вторая ошибка, как я писал выше, будет за нехваткой в тесте бота для отправки сообщений, мы добавим и бота.
- Третья ошибка уже скажет, что в конце код ответа не 429, как мы ожидали, а 200. То есть не хватает реализации собственно троттлинга. Сделаем её, и тест пройдет.
Как нетрудно догадаться, написание промпта для кодиума под этот тест, проверка и небольшая рихтовка результата займет ну максимум минуты две-три. Что конечно быстрее, чем писать то же самое руками. Так что успешный успех от использования ИИ очевиден.
Стоит однако заметить, что это не очень сложный пример. Во-первых, тестирование бэкенд-путей это наверное самое частое для чего пишутся тесты в Ларавеле. Примеров в сети валом. Однако ИИ не сплоховал, и не просто сделал какой-то примерный запрос, а взял из других тестов в точности тот, что нужен.
Во-вторых, ИИ очень хорошо знает как работает в Ларавеле троттлинг. Я проверял, допрашивал в чат-режиме, узнал много нового. Но собственно для этого мы его и позвали — чтобы он применил свои умения и быстро написал тест.
Также стоит отметить, что можно было бы зарефакторить отправку уведомления в какой-нибудь хелпер-метод для краткости, и просто вызывать this→postNotification()
или типа того. И кодиум зачастую так делает. Но в данном случае совершенно правильно оставлено как есть, так как мы тестируем троттлинг именно этого роута, и хотим чтобы было лучше видно, что конкретно происходит.
В следующий раз приведу примеры галлюцинаций и ошибок чата жопотэ при создании юнит-тестов. А пока в качестве промежуточного вывода хочу сказать вот что. Противники TDD и схожих методик основным аргументом всегда приводят, что написание тестов требует кучу времени. Так вот, ИИ уделывает этот аргумент как бог черепаху. Если вновь тестируемая фича стандартна, сразу проси ИИ, скорее всего тест будет точным или очень близким к таковому. Если не очень стандартна, или совсем не стандартна, создай первый feature-тест сам, а потом кодиум сгенерит тебе по запросу все нужные вариации на краевые и смежные случаи. В общем, включай чат жопотэ и пиши тесты. От них сплошная польза.