Привет Хабр! Меня зовут Антон и я 4 года работаю QA . За это время я успел пройти путь от книжки Савина (куда же без неё) до организации процессов тестирования на небольших проектах.
В частности, в процессе автоматизации тестирования иногда появляются интересные задачи, к которым, на первый взгляд, абсолютно непонятно как подступиться. Об одной из таких задач сегодня и пойдёт речь.
Дано
На проекте есть Telegram бот с админкой. Соответственно действия которые произведены в админке отражаются для клиента в боте.
Понятно, что можно протестировать работу админки отдельно, потом для бота мокнуть внешние зависимости и проверить его в отрыве от остального функционала. Но что же с E2E?
Определение требований
Нужен инструмент, который позволит:
Тестировать бота в привязке к админке в вебе
Работать с UI тестами
Работать с API тестами
(желательно) Не зависеть от выбора инструмента автоматизированного тестирования
Поиск готовых решений
По запросам похожим на “Автоматизация тестирования бота telegram” - несколько десятков статей про юнит-тесты, несколько видео про тестирование диалога с ботом и ничего по интересующей конкретно меня теме, возможно плохо искал, но я правда старался.
В какой-то момент даже нашёл относительно близкое к желаемому - фреймворк Botium, но, увы, у него не оказалось коннектора для Telegram. Так что я всё таки решился изобрести свой велосипед…
Идея
Первая идея, мне нужен собственный телеграмм бот, который будет слушать тестируемого бота и сам посылать к нему запросы и возвращать полученные ответы. Возникло сразу две проблемы:
В процессе изучения Telegram Bot API оказалось, что один бот не может писать другому боту и вообще “слышать” его.
Все библиотеки реализующие ботов как правило асинхронные, а тесты у меня вполне себе синхронны.
Всё это осложнялось тем, что я никогда не имел ничего общего с разработкой ботов. В общем, образовался небольшой тупичок, из которого предстояло как-то найти выход.
Формирование решения
Итак, план, в целом, ясен, осталось только решить образовавшиеся проблемы.
Telegram API
С первой проблемой помог Хабр, а именно эта статья.
Оказалось у Telegram, помимо Telegram Bot API, есть ещё и Telegram API, который, как я понял, подразуме��ался как способ создания собственного Telegram клиента. Но так же это API даёт такую интересную возможность как создание, так называемых, юзер-ботов, которые работают под обычными пользователями, что позволяет такому боту взаимодействовать с обычным ботом написанным на Telegram Bot API.
Проблема асинхронности
Тесты синхронные, а бот-тестировщик асинхронный. А такая ли это проблема? Пусть бот работает независимо от тестов, бот будет просто читать и записывать сообщения в какую-нибудь очередь и ждать команд от тестов на отправку.
В итоге мы имеем следующую схему:
Есть тесты, они просто записывают куда-то команды боту-тестировщику на отправку сообщения, либо читают сообщения, которые он прочитал и записал в очередь.
Сам бот-тестировщик работает абсолютно независимо от тестов. Просто ловит событие полученного сообщения от тестируемого бота и слушает очередь, в которую тесты записывают сообщения на отправку. Когда в очереди появляются записи, бот отправляет текст записи тестируемому боту.
Выбор инструментов
Реализовать свою затею решил на самом “ботовом” языке программирования (ну и на самом мне близком) - Python.
Есть несколько библиотек, которые реализуют взаимодействие через Telegram API, мне приглянулся Telethon.
Для реализации очереди сообщений использовал сервер Redis и одноимённую библиотеку Python.
Написание кода
Настройка сессии
Опустим описание процесса регистрации приложения на my.telegram.org и приступим к реализации самого бота. (подробнее о регистрации можно узнать тут)
from telethon import TelegramClient, sync
# Название сессии
session = 'tester_bot'
# Api ID и Api Hash полученные на my.telegram.org
api_id = 12345678
api_hash = '123456789qwerty987654321'
client = TelegramClient(session, api_id, api_hash)
async def main():
# Выводим в консоль данные о текущем пользователе, для проверки
me = await client.get_me()
print(me.stringify())
# Сюда в дальнейшем добавим вызов метода отправки сообщений
# Бот будет запущен пока мы сами не завершим его работу
await client.run_until_disconnected()
if __name__ == '__main__':
with client:
client.loop.run_until_complete(main())
После запуска скрипта в консоли надо ввести телефон от аккаунта Telegram, а затем код подтверждения. Потом в папке со скриптом должен создаться файл .session, который сохраняет вашу сессию и позволяет запускать бота без повторной авторизации через телефон + код.
Важно! Пока разбирался с библиотекой, Telegram счёл мои действия подозрительными, поэтому просто обрывал все сессии с моего IP. В дальнейшем я смог создать стабильную сессию, только с использованием VPN.
Очень рекомендую при получении кода в telegram заходить в официальный клиент с другого IP и устройства (например с телефона через мобильный интернет). И вообще стараться не дёргаться лишний раз до создания сессии.
Что ж, сессия настроена, но бот пока не может ни читать, ни отправлять сообщения
Redis
Для чтения и отправки сообщений из тестов потребуется Redis. Запускаем redis-server и добавляем вызов соединения с ним в скрипт.
from redis import Redis
redis = Redis(host=localhost, port=6379)Чтение сообщений
Читаем сообщения и записываем их в очередь для полученных сообщений в Redis.
@client.on(events.NewMessage())
async def handle_new_message(event):
try:
if event.is_private:
sender_username = await event.get_sender().username
if sender_username == "username тестируемого бота":
# Записываем полученное сообщение от тестируемого бота в очередь
redis.rpush('response_messages', message.message)
except Exception as e:
print(f'Ошибка при чтении сообщения: {e}')Отправка сообщений
А теперь читаем сообщения из очереди на отправку и отправляем их в Telegram.
async def handle_messages_to_send():
while True:
try:
# Проверяем, что очередь не пустая
if redis.llen('request_messages') != 0:
# Забираем сообщение из очереди на отправку, декодируем и отправляем
message = redis.rpop('request_messages').decode()
await client.send_message("username тестируемого бота", message)
except Exception as e:
print(e)
await asyncio.sleep(5)Не забываем добавить вызов этой функции в main.
На этом базовая версия бота готова! Просто запускаем этот скрипт и забываем. Можно переходить к разработке самих тестов.
Тесты
На самих тестах зацикливаться не буду. Работаем с redis как будто это и есть бот. Покажу на примере pytest.
@pytest.fixture()
def tester_bot():
redis = Redis(host='localhost', port=6379)
yield redis
# Чистим очереди после выполнения тестов
redis.delete('response_messages')
redis.delete('request_messages')
def test_response_message(tester_bot):
# Выполяем какие-то действия через браузер или API
...
# Получаем сообщение от бота
message = tester_bot.rpop('response_messages').decode()
assert message == 'Ожидаемое сообщение'
def test_request_message(tester_bot)
# Закидываем сообщение в очередь на отправку
tester_bot.rpush('request_messages', 'Привет')
# Добавляем какое-нибудь ожидание
time.sleep(2)
# Получаем ответ от бота
message = tester_bot.rpop('response_messages').decode()
assert message == 'Привет, я бот! Получил твоё сообщение!'По поводу ожиданий. Чтобы не ждать "глупо", можно реализовать ожидание, пока длина очереди полученных сообщений не изменится, хоть на том же WebDriverWait из Selenium.
Итог
Готово! Бот работает и отлично подключается к тестам, так же абсолютно не важно на каком языке вы пишите тесты, нужно только реализовать в тестах подключение к Redis. Конечно, для полноценной интеграции таких тестов в проект, необходимо дополнительно обернуть реализацию в удобный интерфейс, но целью статьи было показать саму концепцию таким же QA в поисках решения как и я!
Уверен, что всё что-то реализовано не очень хорошо. Если так, то буду только рад обсудить это в комментариях!
