如何使用 rspec 测试 Telegram 机器人
How to test Telegram bot using rspec
我正在构建一个简单的机器人,我需要测试以下方法,但无论我做什么,我 运行 都进入了一个看起来像无限循环的状态。
我要测试的方法
def main_method(token)
@id_array = []
Telegram::Bot::Client.run(token) do |bot|
bot.listen do |message|
@id_array.push(message.chat.id) unless @id_array.include?(message.chat.id)
case message.text
when '/quote'
quote, author = RandomQuote.new.get_quote!
bot.api.send_message(chat_id: message.chat.id, text: "#{quote}\n\t- Author: #{author}")
when '/joke'
bot.api.send_message(chat_id: message.chat.id, text: "What's your name?")
bot.listen do |name|
if name
@joke = Jokes.new.random_joke(name.text)
break
end
end
bot.api.send_message(chat_id: message.chat.id, text: @joke.to_s)
when '/help'
bot.api.send_message(chat_id: message.chat.id, text: 'Type /joke to receive a custom joke with your name')
bot.api.send_message(chat_id: message.chat.id, text: 'Type /quote to receive an inspirational quote')
end
end
end
结束
我的机器人正在工作并完成它的任务,我只需要任何测试用例来证明它的功能。
好吧,这并不友好 "infinite loop",它是事件侦听器 (bot.listen
),它有点轮询某些端口以获取某些消息,然后触发反应关于这次活动。
因此,您需要使用 OOP 逻辑将此方法拆分为小块,因为一个特定方法有大量用例,然后使用模拟用法将每个块作为专用 class 进行测试。
要处理初始化,您可以使用 VCR 磁带将真实请求存入 API,并将令牌替换为您的测试令牌。
例如,我们可以 mock main_method 以确保: listener 被调用,像这样:
class MyRubyBot
def main_method(token)
...
end
end
around do |example|
VCR.use_cassette('telegram_bot') do
example.run
end
end
it 'fires run on telegram bot' do
expect(telegram_bot).to receive(:run).with("YOU_TEST_TOKEN")
MyRubyBot.new.main_method("YOU_TEST_TOKEN")
end
it 'fires listener' do
expect_any_instance_of(Telegram::Bot::Client).to receive(:listen)
MyRubyBot.new.main_method("YOU_TEST_TOKEN")
end
然后你需要将每个when
的逻辑放入专用的class,例如:
class QuoteMessage
attr_accessor :bot, :message
def initialize(bot:, message:)
self.bot = bot
self.message = message
end
def send_response
quote, author = RandomQuote.new.get_quote!
bot.api.send_message(chat_id: message.chat.id, text: "#{quote}\n\t- Author: #{author}")
end
end
class QuoteMessage
attr_accessor :bot, :message, :joke
def initialize(bot:, message:)
self.bot = bot
self.message = message
end
def send_response
bot.api.send_message(chat_id: message.chat.id, text: "What's your name?")
bot.listen do |name|
if name
@joke = Jokes.new.random_joke(name.text)
break
end
end
bot.api.send_message(chat_id: message.chat.id, text: @joke.to_s)
end
end
class HelpMessage
attr_accessor :bot, :message
def initialize(bot:, message:)
self.bot = bot
self.message = message
end
def send_response
bot.api.send_message(chat_id: message.chat.id, text: 'Type /joke to receive a custom joke with your name')
bot.api.send_message(chat_id: message.chat.id, text: 'Type /quote to receive an inspirational quote')
end
end
太好了,现在我们可以为模拟机器人 class 单独测试每个 class,举个简单的例子:
describe QuoteMessage do
let(:bot) { double }
let(:api) { double }
let(:message) { double }
it 'fires send_message' do
expect(bot).to receive(:api).and_return(api)
expect(api).to receive(:send_message).with(...)
described_class.new(bot: bot, message: message).send_response
end
end
# ... same logic for other classes
快到了!现在我们可以用新的 MessageHandler class 稍微重构你的 main_method,像这样:
def main_method(token)
@id_array = []
Telegram::Bot::Client.run(token) do |bot|
bot.listen do |message|
@id_array.push(message.chat.id) unless @id_array.include?(message.chat.id)
MessageHandler.new(bot: bot, message: message).handle_message
end
end
end
class MessageHandler
attr_accessor :bot, :message
def initialize(bot:, message:)
self.bot = bot
self.message = message
end
def handle_message
case message.text
when '/quote'
QuoteMessage.new(bot: bot, message: message).send_response
when '/joke'
joke_message = JokeMessage.new(bot: bot, message: message)
joke_message.send_response
@joke = joke_message.joke
when '/help'
HelpMessage.new(bot: bot, message: message).send_response
end
end
end
所以,现在我们可以通过 and_yield
方法测试 MessageHandler
的火灾:
it 'fires MessageHandler' do
message = 'any_message'
expect(bot).to receive(:listen).and_yield(MessageHandler.new(bot: bot, message: message))
MyRubyBot.new.main_method("YOU_TEST_TOKEN")
end
至少,我们需要测试 MessageHandler,现在应该很容易,例如:
describe MessageHandler do
subject { described_class.new(bot: bot, message: message) }
let(:bot) { double }
let(:api) { double }
context 'when quote' do
let(:message) { '/quote' }
let(:quote_message) { instance_double QuoteMessage }
it 'fires QuoteMessage instance' do
expect(QuoteMessage).to receive(:new).with(bot: bot, message: message).and_return quote_message
expect(quote_message).to receive(:send_response)
subject.handle_message
end
end
end
当然,我可能在某些细节上有误,但总的来说我想采用这种方法。
JFYI:尝试提出更具体的问题,因为看起来您在这里有一项任务,并且您决定将其以相同的形式放在这里,所以,这不是最好的方式,这就是为什么许多人忽略它的原因。希望对你有帮助,保重!
我正在构建一个简单的机器人,我需要测试以下方法,但无论我做什么,我 运行 都进入了一个看起来像无限循环的状态。
我要测试的方法
def main_method(token)
@id_array = []
Telegram::Bot::Client.run(token) do |bot|
bot.listen do |message|
@id_array.push(message.chat.id) unless @id_array.include?(message.chat.id)
case message.text
when '/quote'
quote, author = RandomQuote.new.get_quote!
bot.api.send_message(chat_id: message.chat.id, text: "#{quote}\n\t- Author: #{author}")
when '/joke'
bot.api.send_message(chat_id: message.chat.id, text: "What's your name?")
bot.listen do |name|
if name
@joke = Jokes.new.random_joke(name.text)
break
end
end
bot.api.send_message(chat_id: message.chat.id, text: @joke.to_s)
when '/help'
bot.api.send_message(chat_id: message.chat.id, text: 'Type /joke to receive a custom joke with your name')
bot.api.send_message(chat_id: message.chat.id, text: 'Type /quote to receive an inspirational quote')
end
end
end
结束
我的机器人正在工作并完成它的任务,我只需要任何测试用例来证明它的功能。
好吧,这并不友好 "infinite loop",它是事件侦听器 (bot.listen
),它有点轮询某些端口以获取某些消息,然后触发反应关于这次活动。
因此,您需要使用 OOP 逻辑将此方法拆分为小块,因为一个特定方法有大量用例,然后使用模拟用法将每个块作为专用 class 进行测试。
要处理初始化,您可以使用 VCR 磁带将真实请求存入 API,并将令牌替换为您的测试令牌。
例如,我们可以 mock main_method 以确保: listener 被调用,像这样:
class MyRubyBot
def main_method(token)
...
end
end
around do |example|
VCR.use_cassette('telegram_bot') do
example.run
end
end
it 'fires run on telegram bot' do
expect(telegram_bot).to receive(:run).with("YOU_TEST_TOKEN")
MyRubyBot.new.main_method("YOU_TEST_TOKEN")
end
it 'fires listener' do
expect_any_instance_of(Telegram::Bot::Client).to receive(:listen)
MyRubyBot.new.main_method("YOU_TEST_TOKEN")
end
然后你需要将每个when
的逻辑放入专用的class,例如:
class QuoteMessage
attr_accessor :bot, :message
def initialize(bot:, message:)
self.bot = bot
self.message = message
end
def send_response
quote, author = RandomQuote.new.get_quote!
bot.api.send_message(chat_id: message.chat.id, text: "#{quote}\n\t- Author: #{author}")
end
end
class QuoteMessage
attr_accessor :bot, :message, :joke
def initialize(bot:, message:)
self.bot = bot
self.message = message
end
def send_response
bot.api.send_message(chat_id: message.chat.id, text: "What's your name?")
bot.listen do |name|
if name
@joke = Jokes.new.random_joke(name.text)
break
end
end
bot.api.send_message(chat_id: message.chat.id, text: @joke.to_s)
end
end
class HelpMessage
attr_accessor :bot, :message
def initialize(bot:, message:)
self.bot = bot
self.message = message
end
def send_response
bot.api.send_message(chat_id: message.chat.id, text: 'Type /joke to receive a custom joke with your name')
bot.api.send_message(chat_id: message.chat.id, text: 'Type /quote to receive an inspirational quote')
end
end
太好了,现在我们可以为模拟机器人 class 单独测试每个 class,举个简单的例子:
describe QuoteMessage do
let(:bot) { double }
let(:api) { double }
let(:message) { double }
it 'fires send_message' do
expect(bot).to receive(:api).and_return(api)
expect(api).to receive(:send_message).with(...)
described_class.new(bot: bot, message: message).send_response
end
end
# ... same logic for other classes
快到了!现在我们可以用新的 MessageHandler class 稍微重构你的 main_method,像这样:
def main_method(token)
@id_array = []
Telegram::Bot::Client.run(token) do |bot|
bot.listen do |message|
@id_array.push(message.chat.id) unless @id_array.include?(message.chat.id)
MessageHandler.new(bot: bot, message: message).handle_message
end
end
end
class MessageHandler
attr_accessor :bot, :message
def initialize(bot:, message:)
self.bot = bot
self.message = message
end
def handle_message
case message.text
when '/quote'
QuoteMessage.new(bot: bot, message: message).send_response
when '/joke'
joke_message = JokeMessage.new(bot: bot, message: message)
joke_message.send_response
@joke = joke_message.joke
when '/help'
HelpMessage.new(bot: bot, message: message).send_response
end
end
end
所以,现在我们可以通过 and_yield
方法测试 MessageHandler
的火灾:
it 'fires MessageHandler' do
message = 'any_message'
expect(bot).to receive(:listen).and_yield(MessageHandler.new(bot: bot, message: message))
MyRubyBot.new.main_method("YOU_TEST_TOKEN")
end
至少,我们需要测试 MessageHandler,现在应该很容易,例如:
describe MessageHandler do
subject { described_class.new(bot: bot, message: message) }
let(:bot) { double }
let(:api) { double }
context 'when quote' do
let(:message) { '/quote' }
let(:quote_message) { instance_double QuoteMessage }
it 'fires QuoteMessage instance' do
expect(QuoteMessage).to receive(:new).with(bot: bot, message: message).and_return quote_message
expect(quote_message).to receive(:send_response)
subject.handle_message
end
end
end
当然,我可能在某些细节上有误,但总的来说我想采用这种方法。
JFYI:尝试提出更具体的问题,因为看起来您在这里有一项任务,并且您决定将其以相同的形式放在这里,所以,这不是最好的方式,这就是为什么许多人忽略它的原因。希望对你有帮助,保重!