RSpec测试中方法Stub失效触发Errno::ENOENT报错的问题咨询
环境信息
- Ruby版本:2.7.2
- Rspec版本:3.12.0
问题场景
我在学习App Academy Open的井字棋项目,大部分测试已通过,但最后几个关于Game#play方法的测试卡壳了。之前成功Stub过方法调用,但这次Stub完全没生效。
核心代码片段
Game类的#play方法
def play while @board.empty_positions? puts @board.print position = @current_player.get_position @board.place_mark(position, @current_player.mark) if @board.win?(@current_player.mark) puts "Player #{@current_player.mark} wins!" return else switch_turn end end puts "The game ended in a draw!" end
测试代码
RSpec.describe Game do let(:game) { Game.new(:X, :O) } # ... describe "#play" do before :each do @board = game.instance_variable_get(:@board) @board.place_mark([0, 0], :X) @board.place_mark([0, 1], :O) @board.place_mark([0, 2], :X) @board.place_mark([1, 0], :O) @board.place_mark([2, 0], :X) @board.place_mark([1, 1], :O) @board.place_mark([2, 2], :X) end it "should call Board#place_mark" do @current_player = game.instance_variable_get(:@current_player) allow(@current_player).to receive(:get_position).and_return([1, 2]) expect(@board).to receive(:place_mark) game.play end end end
HumanPlayer的#get_position方法
def get_position puts "Player #{@mark}, enter two numbers representing a position in the format `row col`" position = gets.chomp.split(" ") if position.length != 2 || # not 2 characters position.any? { |n| n.to_i.to_s != n } # not all numeric raise "Invalid Position" end position.map(&:to_i) end
Board的#place_mark方法
def place_mark(position, mark) raise "Placement Invalid" if !valid?(position) || !empty?(position) row = position[0] col = position[1] @grid[row][col] = mark end
报错信息
Game Instance Methods #play should call Board#place_mark Failure/Error: position = gets.chomp.split(" ") Errno::ENOENT: No such file or directory @ rb_sysopen - spec/2_game_spec.rb:85 # ./lib/human_player.rb:11:in `gets' # ./lib/human_player.rb:11:in `gets' # ./lib/human_player.rb:11:in `get_position' # ./lib/game.rb:20:in `play' # ./spec/2_game_spec.rb:92:in `block (4 levels) in <top (required)>'
我原本以为Stub了HumanPlayer#get_position返回[1,2]就能跳过真实的gets调用,但实际Board#place_mark好像没生效,导致循环再次调用get_position,触发gets时报错。
尝试过的无效方案
- Stub
gets方法:
it "should call Board#place_mark" do @current_player = game.instance_variable_get(:@current_player) allow(@current_player).to receive(:gets).and_return("1 2") expect(@board).to receive(:place_mark) game.play end
- 使用
allow_any_instance_of(HumanPlayer):
it "should call Board#place_mark" do @current_player = game.instance_variable_get(:@current_player) allow_any_instance_of(HumanPlayer).to receive(:get_position).and_return([1, 2]) expect(@board).to receive(:place_mark) game.play end
这个方案导致棋盘无限打印循环。
解决方案
问题根源
- Stub对象错误:before块里下了7步棋(X走4次、O走3次),此时游戏的当前玩家是O,但你Stub的是游戏初始化时的X玩家,实际调用
get_position的是未被Stub的O玩家实例,触发了真实的gets方法。 - 无限循环原因:用
allow_any_instance_of时,每次调用get_position都返回[1,2],第一次落子后该位置已被占据,place_mark抛出异常,循环继续重复调用同一个无效位置,导致无限循环。
修复后的测试代码
describe "#play" do before :each do @board = game.instance_variable_get(:@board) @board.place_mark([0, 0], :X) @board.place_mark([0, 1], :O) @board.place_mark([0, 2], :X) @board.place_mark([1, 0], :O) @board.place_mark([2, 0], :X) @board.place_mark([1, 1], :O) @board.place_mark([2, 2], :X) # 获取当前真实的玩家(此时是O) @current_player = game.instance_variable_get(:@current_player) # 屏蔽控制台打印,避免测试输出混乱 allow(@board).to receive(:print).and_return("mock board") allow($stdout).to receive(:puts) end it "should call Board#place_mark and end the game with O's win" do # Stub正确的当前玩家实例 allow(@current_player).to receive(:get_position).and_return([1, 2]) # 明确期望place_mark的调用参数 expect(@board).to receive(:place_mark).with([1, 2], :O) # 验证游戏会因O获胜终止 expect { game.play }.to output(/Player O wins!/).to_stdout end end
额外说明
- 落子
[1,2]后,O的第1行形成O-O-O的获胜线,游戏会直接终止,不会进入下一轮循环。 - 屏蔽
puts和board.print的输出,能让测试结果更清晰,避免无关信息干扰。
内容的提问来源于stack exchange,提问作者Abdul
相关产品推荐
相关产品推荐

