如何为包含 'gets.chomp' 的变量的 Ruby 方法编写 Rspec 测试?
How do I write an Rspec test for a Ruby method that contains variable that contains 'gets.chomp'?
我查看了其他测试示例,但大多数其他示例不一定具有等于 'gets.chomp.downcase' 的变量,这让我很难进行测试。
剩下的是国际象棋游戏,但我正在努力做到这一点,所以如果您在介绍中输入 "new",它将调用方法#instructions,它会显示说明并询问是否你准备好玩了。
这是方法#introduction
def introduction
puts " \n"
print " Welcome to chess! "
puts "What would you like to do?"
puts "
* Start a new Game -> Enter 'new'
* Load a saved Game -> Enter 'load'
* Exit -> Enter 'exit'"
input = gets.chomp.downcase
if input == "new"
instructions
elsif input == "load"
load_game
elsif input == "exit"
exit!
else
introduction
end
end
这是我对其进行的测试,它一直显示错误 "Failure/Error: input = gets.chomp.downcase"
"NoMethodError:
nil:NilClass
的未定义方法 `chomp'
describe Game do
describe "#introduction" do
it "starts a new game with input 'new'" do
io = StringIO.new
io.puts "new"
$stdin = io
game = Game.new
game.introduction
allow(game).to receive(:gets).and_return(io.gets)
expect(game).to receive(:instructions)
end
end
end
使用代码注入而不是模拟或存根
您的方法存在多个问题。我不会一一列举,而是重点关注三个关键错误:
- 单元测试通常应测试方法结果,而不是复制内部结构。
- 您正在尝试使用 #allow 而不先定义双精度数。
- 您似乎在尝试设置消息期望,而不是使用存根 return 一个值。
您的代码和测试肯定还有其他问题,但一旦您从测试用例中消除对#gets 的依赖,我就会从这里开始。例如,要测试方法中的各种路径,您应该为每个 expected value 配置一系列测试,其中 #and_return 显式 returns new
, load
,或其他什么。
更务实地说,您很可能会遇到困难,因为您首先编写了代码,现在正在尝试改进测试。虽然您可能会通过猴子修补程序使其可测试 post-facto,但您最好重构代码以允许在测试中直接注入。例如:
def show_prompt
print prompt =<<~"EOF"
Welcome to chess! What would you like to do?
* Start a new Game -> Enter "new"
* Load a saved Game -> Enter "load"
* Exit -> Enter "exit"
Selection:\s
EOF
end
def introduction input=nil
show_prompt
# Use an injected input value, if present.
input ||= gets.chomp.downcase
case input
when "new" then instructions
when "load" then load_game
when "exit" then exit!
else introduction
end
end
这避免了首先对对象进行存根或模拟的需要。您的测试现在可以简单地调用带有或不带显式值的#introduction。这使您可以将时间花在测试逻辑分支和方法输出上,而不是编写大量脚手架来支持模拟 IO#gets 调用或避免与 nil 相关的异常。
我查看了其他测试示例,但大多数其他示例不一定具有等于 'gets.chomp.downcase' 的变量,这让我很难进行测试。
剩下的是国际象棋游戏,但我正在努力做到这一点,所以如果您在介绍中输入 "new",它将调用方法#instructions,它会显示说明并询问是否你准备好玩了。
这是方法#introduction
def introduction
puts " \n"
print " Welcome to chess! "
puts "What would you like to do?"
puts "
* Start a new Game -> Enter 'new'
* Load a saved Game -> Enter 'load'
* Exit -> Enter 'exit'"
input = gets.chomp.downcase
if input == "new"
instructions
elsif input == "load"
load_game
elsif input == "exit"
exit!
else
introduction
end
end
这是我对其进行的测试,它一直显示错误 "Failure/Error: input = gets.chomp.downcase"
"NoMethodError: nil:NilClass
的未定义方法 `chomp'describe Game do
describe "#introduction" do
it "starts a new game with input 'new'" do
io = StringIO.new
io.puts "new"
$stdin = io
game = Game.new
game.introduction
allow(game).to receive(:gets).and_return(io.gets)
expect(game).to receive(:instructions)
end
end
end
使用代码注入而不是模拟或存根
您的方法存在多个问题。我不会一一列举,而是重点关注三个关键错误:
- 单元测试通常应测试方法结果,而不是复制内部结构。
- 您正在尝试使用 #allow 而不先定义双精度数。
- 您似乎在尝试设置消息期望,而不是使用存根 return 一个值。
您的代码和测试肯定还有其他问题,但一旦您从测试用例中消除对#gets 的依赖,我就会从这里开始。例如,要测试方法中的各种路径,您应该为每个 expected value 配置一系列测试,其中 #and_return 显式 returns new
, load
,或其他什么。
更务实地说,您很可能会遇到困难,因为您首先编写了代码,现在正在尝试改进测试。虽然您可能会通过猴子修补程序使其可测试 post-facto,但您最好重构代码以允许在测试中直接注入。例如:
def show_prompt
print prompt =<<~"EOF"
Welcome to chess! What would you like to do?
* Start a new Game -> Enter "new"
* Load a saved Game -> Enter "load"
* Exit -> Enter "exit"
Selection:\s
EOF
end
def introduction input=nil
show_prompt
# Use an injected input value, if present.
input ||= gets.chomp.downcase
case input
when "new" then instructions
when "load" then load_game
when "exit" then exit!
else introduction
end
end
这避免了首先对对象进行存根或模拟的需要。您的测试现在可以简单地调用带有或不带显式值的#introduction。这使您可以将时间花在测试逻辑分支和方法输出上,而不是编写大量脚手架来支持模拟 IO#gets 调用或避免与 nil 相关的异常。