RSpec:使用方法存根来测试交互式用户输入

我是Ruby和RSpec的新手,已经花了好几个小时尝试编写测试交互式tic tac toe程序的第一步,但是没有找到任何有用的答案来解决我得到的错误:

> bundle exec rspec --format documentation Do you want to play tic-tac-toe? (y/n) An error occurred while loading ./spec/tic_tac_toe_spec.rb. Failure/Error: choice = gets.chomp.downcase Errno::ENOENT: No such file or directory @ rb_sysopen - --format # ./lib/tic_tac_toe.rb:70:in `gets' # ./lib/tic_tac_toe.rb:70:in `gets' # ./lib/tic_tac_toe.rb:70:in `start_game' # ./lib/tic_tac_toe.rb:144:in `' # ./spec/tic_tac_toe_spec.rb:1:in `require' # ./spec/tic_tac_toe_spec.rb:1:in `' No examples found. Finished in 0.0004 seconds (files took 0.08415 seconds to load) 0 examples, 0 failures, 1 error occurred outside of examples 

当您开始游戏时,它会提示您是否要播放(是/否)。 我已经尝试过使用模拟和存根的不同变体但总是会得到同样的错误。 我想我错过了一些非常基本但却看不到的东西。

问题 :如何充分解决(使用RSpec)游戏的第一个提示?

游戏使用独立的方法( start_game ),但不是一个类来启动。 我正在寻找某种方式模拟或存储一些默认输入(如yn ),因为我希望测试自动化。 我看到的所有示例都使用类来启动程序(而不是像这里的独立方法)。

目前在spec/tic_tac_toe_spec.rb有一些let关键字:output:game我从某个例子中找到的:game ,但显然这不起作用。

编辑我想测试以下方法。 RSpec代码在choice = gets.chomp.downcase行上保持阻塞。

 def start_game puts "Do you want to play tic-tac-toe? (y/n)" choice = gets.chomp.downcase unless ["y","n"].include?(choice) abort("Please answer with y or n.") end case choice when "y" puts "Ok, let's start!" b = Board.new puts "Enter name of player 1 (O)" player1name = gets.chomp player1 = Player.new(player1name) puts "Now enter name of player 2 (X)" player2name = gets.chomp player2 = Player.new(player2name) play_game(player1, player2, b) when "n" puts "Your loss." end end #start_game 

您收到错误是因为您将--format documentation作为参数传递给RSpec。

gets读取的ARGV。 这还包括RSpec参数。 您应该使用STDIN.gets以便只读取标准输入而不是参数。

您可以在此问题中阅读更多信息: https : //stackoverflow.com/a/19799223/6156030

一个简单的方法可能是:

 it 'works' do allow($stdin).to receive(:gets).and_return('y') expect { start_game } # (Or however you run it!) .to output("Ok, let's start!") .to_stdout end 

您还可以为gets指定多个返回值,这些值将在测试运行时按顺序使用。

您似乎已经开始(但未完全实现)的替代方法是将一个显式output (可能是一天input )对象注入到游戏中。

从纯粹主义者的角度来看,这确实是一种更清晰的方法 – 即不会像$stdin那样对全局对象进行存根 – 但对于您的初始版本的代码来说可能有点过分。 除非你打算做一些像运行并行规范这样的事情,否则我不会担心。

编辑:在更详细地查看您的实际代码后,我看到了问题。 您正在定义全局方法,这些方法正在执行多项操作并且紧密耦合。 这使得编写测试更加困难!

这里我添加了一个工作测试示例:

https://github.com/tom-lord/tic_tac_toe_rspec/commit/840df0b7f1380296db97feff0cd3ca995c5c6ee3

但是,为了简化这一点,我的建议是在适当的类中定义所有每个方法,并使代码更少程序化。 (即不要只是让一个方法的结束以长序列调用下一个方法!)这种重构可能超出了StackOverflow答案的范围。

你需要在你的规范中存根和模拟 gets方法:

 yes_gets = double("yes_gets") allow($stdin).to receive(:gets).and_return(yes_gets) 

然后你可以让它响应#chomp

 expect(yes_gets).to receive(:chomp).and_return('Y') 

您可以通过返回此双重对象本身来覆盖类似的downcase方法调用。

您也可以为'N'情况做模拟对象的类似工作,当玩家输入N(否)时,您希望游戏退出:

 no_gets = double("no_gets") allow($stdin).to receive(:gets).and_return(no_gets) expect(no_gets).to receive(:chomp).and_return('N')