Автоматизированное тестирование ботов для Telegram

Оригинал поста на хабре

Кажется, что время - это река, которую внезапно переклинило, и она решила течь по кругу. Именно такое впечатление складывается на первый взгляд, когда видишь, что вновь стали популярны боты в мессенджерах. Но это впечатление обманчиво. Изменилось очень многое - мощности, которые стоят за ботами, возможность обработки ими мультимедиа информации, наличие информации о пользователях, круг охвата… В общем, это явно не ностальгический тренд, а реально полезная технология, которая будет развиваться и дальше.

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

Для некоего личного проекта мне захотелось написать бота с довольно сложной ветвящейся логикой (например, это может быть система поддержки или диагностики с глубокой вложенностью). При этом граф данной логики имеет огромное количество разветвлений. В общем, быстро стало очевидно, что без автоматизированного тестирования не обойтись - иначе что-то точно упущу из внимания. И насколько же сильно я удивился, когда узнал, что способа тестировать логику ботов просто нет!

Конечно, можно зарегистрировать дополнительного бота для тестирования, но это вариант кривой и некрасивый. Обращение ко внешнему апи во время тестов, заглушка, которая не даст общаться с ботом кому попало, ограничение на скорость отправки сообщений раз в секунду… Если слать сообщение раз в секунду, то граф из каких-то 60 вершин будет тестироваться уже больше минуты! И я уже не говорю о том, что у нас нет никакой возможности смоделировать возросшую нагрузку на бота, при которой он упрётся в ограничение в 30 сообщений в секунду… В общем, я понял, что опять придётся делать что-то своё.

Два решения задачи

Надстройка

Первый вариант с огромной скоростью реализации - это просто надстройка над самой популярной Node.JS библиотекой для реализации Telegram ботов - node-telegram-bot-api. Сделано довольно просто - переписывается обработчик события на отсылку данных, после чего мы обрабатываем данные своим методом и посылаем вручную сгенерированный запрос. Выходит примерно так:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
describe('Telegram Test', ()=> {
const myBot = new TestBot(telegramBot);
let testChat = 0;

it('should greet Masha', () => {
const telegramTest = new TelegramTest(telegramBot);
testChat++;
return telegramTest.sendUpdate(testChat, '/ping')
.then((data)=> {
if (data.text === 'pong') {
return telegramTest.sendUpdate(testChat, '/start');
}
throw new Error(`Wrong answer for ping! (was ${data.text})`);
})
.then(data=> telegramTest.sendUpdate(testChat, data.keyboard[0][0].text))
.then((data)=> {
if (data.text === 'Hello, Masha!') {
return true;
}
throw new Error('Wrong greeting!');
});
});
});

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

Свой сервер API Telegram

Решение выше хорошо для быстрого тестирования каких-то простых вещей и покроет большинство потребностей тестирования. Однако что делать, если мы хотим, скажем, тестировать, как наш бот работает под большой нагрузкой? В этом случае единственный логичный выход - это реализовать свой вариант Telegram API. Звучит довольно страшно, но на самом деле нам нужна простая реализация, которая совместима с текущими библиотеками для ботов и позволяет отправлять клиентские запросы. Кстати, как побочная фича - получается, что мы делаем полноценный сервер, с которым можно работать из любого другого стека технологий - хоть питон, хоть C#, хоть что.

To make the long story short, я сделал и такой вариант. С ним тестирование логики того же бота выглядит примерно так:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
it('should greet Masha', function testFull() {
this.slow(400);
this.timeout(800);
let serverConfig = {port: 9000};
let server = new TelegramServer(serverConfig);
let token = 'sampleToken';
let client = server.getClient(token);
let message = client.makeMessage('/start');
let telegramBot,
testBot;
return server.start()
.then(()=> client.sendMessage(message))
.then(()=> {
let botOptions = {polling: true, baseApiUrl: server.ApiURL};
telegramBot = new TelegramBot(token, botOptions);
testBot = new TestBot(telegramBot);
return client.getUpdates();
})
.then((updates)=> {
console.log(colors.blue(`Client received messages: ${JSON.stringify(updates.result)}`));
if (updates.result.length !== 1) {
throw new Error('updates queue should contain one message!');
}
let keyboard = JSON.parse(updates.result[0].message.reply_markup).keyboard;
message = client.makeMessage(keyboard[0][0].text);
client.sendMessage(message);
return client.getUpdates();
})
.then((updates)=> {
console.log(colors.blue(`Client received messages: ${JSON.stringify(updates.result)}`));
if (updates.result.length !== 1) {
throw new Error('updates queue should contain one message!');
}
if (updates.result[0].message.text !== 'Hello, Masha!') {
throw new Error('Wrong greeting message!');
}
return true;
})
});

То есть, мы поднимаем у себя на локальном порту сервер, дальше стандартным образом настраиваем бота на работу с этим сервером, и просто шлём ему сообщения от ботов и клиентов. Объект клиента можно получить прямо из сервера, или можно написать своего клиента - сервер просто принимает сообщения в стандартном JSON формате по определённому адресу.

TODO

Конечно, осталось огромное количество всяких полезных вещей, которые можно добавить. К примеру, сейчас очередь сообщений хранится просто в массиве в оперативной памяти. Не реализовано эмулирование таймаутов, и поддерживается работа только с одним клиентом одновременно (сервер просто высылает все сообщения клиенту, который обратился к нему за данными от определённого бота, идентифицируемого по токену). Так же есть поддержка только отправки текстовых сообщений. Связано это с тем, что меня на текущий момент интересует только текст.

Первому проекту особо добавить нечего, а второй скорее всего будет развиваться в сторону моих личных потребностей. Но я немного рассчитываю на поддержку open source сообщества. Лицензия MIT, поддержка Node.js 4 и 6 (5 тоже будет работать, но на ней не пройдут тесты, поскольку используемый для теста бот несовместим с Node 5. Впрочем, я уже создал на это pull request), репозитории ждут ваших пулл реквестов с кодом, оформленным по включённой в проект конфигурации линтера. Некое количество тестов тоже есть. Возможно, со временем будет поддержка ботов и для других мессенджеров.

Если вам интересна сама тема ботов, то оставайтесь на связи. Будет интересно.

Ссылки

  1. Первая реализация (надстройка над node-telegram-bot-api);
  2. Вторая реализация (эмуляция Telegram API);
  3. Хорошая статья про борьбу с ограничениями на скорость отправки сообщений на go;
  4. Официальные ограничения на скорость отправки сообщений.