Все записи
6 мин

345 тестов зелёные, а живой запуск выкатил 6 багов: первый смоук-тест Telegram-бота

Building in Publicмышлениеtelegram-боты

Если совсем коротко, то у меня было 345 зелёных тестов, я первый раз запустил бота вживую с реальными ключами, реальным распознаванием голоса и реальной LLM, и тот же самый вечер вскрыл шесть багов, которых не увидел ни один из этих 345 тестов. Так что если вы думаете, что зелёная плашка в терминале означает работающий продукт, то нет, не означает, и я вам сейчас на конкретных шести косяках покажу почему, и заодно расскажу, как делается нормальный смоук-тест телеграм-бота, чтобы вы не радовались раньше времени, как радовался я.

А порадовался я знатно. Когда после недель сборки бот наконец ответил мне голосом и живой репликой, я написал ровно так: «Вау.. у меня вау! я не знаю что еще сказать и как еще потестить. Что написать что выбрать?». Вот оно, то самое чистое ощущение, когда штука, которую ты собирал из спек, тестов и кода, вдруг оживает и говорит с тобой по-человечески. И ровно в этот момент включается ловушка, потому что ты решаешь, что всё, готово, можно показывать людям. А оно не готово.

Почему 345 зелёных тестов меня обманули

Давайте сразу разберёмся, в чём тут фокус. Тест проверяет ровно то, что вы ему сказали проверить, и ровно в том окружении, которое вы ему подсунули. У меня были unit-тесты и E2E, они гоняли логику на моках, на подставных данных, на идеальных сценариях, где callback всегда приходит от правильного юзера, голос всегда чистый, а LLM всегда возвращает аккуратный ответ нужного формата. И в этом мирке всё зелёное, потому что этот мирок я сам и нарисовал.

А реальный прогон — это уже совсем другая планета. Там настоящий Telegram со своими причудами, там whisper, который слышит не то, что я хотел сказать, а то, что я промямлил, там LLM, которая иногда отдаёт null вместо заголовка, там продакшн-докер с правами на файлы, которых нет на моём маке. И вот когда всё это сходится вместе в один живой вечер, вылезает то, что на моках не вылезет никогда, потому что моки по определению не врут так, как врёт жизнь. Это не значит, что тесты бесполезны, наоборот, без них я бы давно утонул. Это значит, что зелёные тесты и работающий продукт — это две разные вещи, и путать их выходит дорого.

Шесть багов, которые увидел только живой прогон

Первый и самый подлый. Когда юзер жмёт инлайн-кнопку, приходит callback, и моя функция, которая достаёт id пользователя, доставала его не из того места, она брала автора сообщения-ответа, а автор там бот. То есть все мои хендлеры на нажатие кнопок получали на вход не юзера, а самого бота, и вешали блокировку на бота же. На тестах с моками этого не видно вообще, там id всегда подсунут руками. А вживую человек жмёт кнопку и попадает в чужую сессию. Кстати, именно про такой класс багов, когда AI путает чью-то личность с чьей-то, я уже писал отдельно в посте про приватность сессий, так что грабли мне знакомые.

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

Третий. Бот пытается прочитать список участников канала и получает от Telegram отказ, мол список недоступен. Причина оказалась бытовая до смешного, бот просто не был администратором канала. Починил, и заодно добавил в middleware режим, при котором если проверка не прошла, бот не падает наглухо, а пропускает дальше с разумным дефолтом. Потому что внешний сервис тебе откажет в самый неподходящий момент, и падать из-за этого нельзя.

Четвёртый, мой любимый по части бытового идиотизма. Кеш для голосовых файлов по умолчанию складывался в системную папку продакшн-докера, а на моём маке такого пути просто нет, да и писать туда нельзя, read-only. Решилось переменной окружения, теперь путь к кешу задаётся через конфиг, а не прибит гвоздями прямо в коде. Это ровно тот случай, когда «у меня на докере работает» и «у меня на маке работает» — это два разных утверждения, а тесты про эту разницу не знали ничего.

Пятый. На одно нажатие кнопки ответ прилетал дважды. Меня это так задело, что я в сердцах написал прямо в сессию: «Я сука нажимаю на новую. ты че ахуел». Дубль ответа — это классика живого Telegram, на моках одно событие всегда остаётся одним событием, а в реальности обработчик может сработать дважды, и ты это видишь только глазами, когда тебе в чат падает два одинаковых сообщения подряд.

Шестой. На абстрактной фразе LLM два раза подряд вернула пустой заголовок цели вместо текста, и валидация это закономерно не пропустила. Это вообще отдельная боль, потому что когда внутри твоего продукта сидит нейросеть, она недетерминирована, а значит твой код обязан переживать тот случай, когда модель отдала мусор или пустоту. На зелёном тесте LLM возвращает что велено, а в проде она живая и иногда тупит.

Что я понял про тестирование ai агентов

Главный вывод тут простой. Тесты ловят регрессии в твоей логике, а смоук-тест ловит враньё реальности, и это всё-таки разные задачи. Когда у тебя внутри продукта живёт ai агент, то есть распознавание голоса плюс LLM плюс внешний Telegram, ни один мок не воспроизведёт всю ту грязь, которая приходит снаружи. Поэтому тестирование ai агентов — это не «прогнал unit и спи спокойно», это обязательный живой прогон руками, где ты сам жмёшь на кнопки, сам диктуешь голосом, сам ломаешь сценарий неудобной фразой про шашлыки. Ровно так же у меня было с астрологией, где я за 7 часов собрал MVP, а потом нашёл 16 косяков в UI, которых сам в упор не видел.

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

После того как все шесть багов были закрыты, я не стал полагаться на память и собрал нормальный E2E test runner на четыре сценария, плюс маленький helper-скрипт, чтобы смотреть логи от живых юзеров. Потому что один раз поймал руками шесть багов, а во второй раз уже хочется, чтобы их ловила машина, и чтобы я смотрел не в терминал с моками, а в реальные логи реальных людей, которые жмут реальные кнопки.

Сухой остаток

Цифры такие. 345 тестов зелёные, и при этом 6 реальных багов всплыли только на первом живом прогоне за один вечер. Это не провал тестов и не провал процесса, это нормальная цена за то, что продукт состоит не из твоей чистой логики, а из твоей логики плюс голос плюс LLM плюс капризный Telegram. Если вы делаете разработку и тестирование mvp, особенно когда внутри сидит нейросеть, заложите отдельный этап на живой смоук перед тем, как звать людей. Не «тесты зелёные значит готово», а «я сам прокликал всё руками, надиктовал голосом, сломал пару сценариев, посмотрел логи, и вот только теперь готово».

Кстати про голос отдельная маленькая радость, потому что в первый раз я надиктовал боту цель шёпотом и потом честно отметил себе: «Так вроде работает. Я шопотом сказал. Поэтому наверно так.) завтра утром запишу более четко». Вот из таких мелочей и собирается понимание, что продукт живой, а не просто «по тестам проходит». Зелёная плашка — это начало проверки, а никак не её конец. Вот и делайте выводы.