0

我在为使用 minitest 在 Ruby 中调用自身(游戏循环)的方法开发单元测试时遇到问题。我一直在尝试用我的输入来存根我试图在所述游戏循环中调用的方法。这是游戏循环:

#main game loop
 def playRound
  #draw board
  @board.printBoard
  #get input
  playerInput = gets.chomp #returns user input without ending newline
 
  #interpret input, quitting or beginning set selection for a player
     case playerInput
      when "q"
       quit
      when "a", "l"
        set = getPlayerSet()
       if(playerInput == "a")
        player = 1
       else
        player = 2
       end
      when "h"
       if @hintsEnabled
        giveHint
        playRound
       else
        puts "Hints are disabled"
        playRound
       end
      else
       puts "Input not recognized."
     end
     if(set != nil)
      #have board test set
      checkSet(set, player)
     end
     #check if player has quitted or there are no more valid sets
     unless @quitted || @board.boardComplete
      playRound
     end
 end

其中大部分最终是无关紧要的,我试图测试的是这个 switch 语句正在调用正确的方法。目前我正试图通过存根被调用的方法来引发错误(我的测试assers_raise的)来规避循环:

def test_playRound_a_input_triggers_getPlayerSet
  @game.stub :getPlayerSet, raise(StandardError) do
   assert_raises(StandardError) do
    simulate_stdin("") { 
      @game.playRound
     }
   end
   end
 end

但是,这种方法似乎不起作用,因为 Minitest 将上述测试的结果记录为消息中的错误

E
错误:
TestGame#test_playRound_a_input_triggers_getPlayerSet:
StandardError:StandardError
test_game.rb:136:in `test_playRound_a_input_triggers_getPlayerSet'

如果有人对我有任何建议或指导,我将不胜感激,因为我不知道出了什么问题

4

1 回答 1

1

我对 minitest 不是很熟悉,但我希望您需要将其包装raise(exception)在一个块中,否则您的测试代码会立即在您的测试中引发异常(而不是由于调用了存根方法)。

就像是:

class CustomTestError < RuntimeError; end
def test_playRound_a_input_triggers_getPlayerSet
  raise_error = -> { raise(CustomTestError) }
  @game.stub(:getPlayerSet, raise_error) do
    assert_raises(CustomTestError) do
      simulate_stdin("") { 
        @game.playRound
      }
    end
  end
end

- 编辑 -

有时,当我在测试一个方法时遇到困难时,这表明我应该重构一些东西以便更容易测试(因此有一个更干净、更简单的界面,以后可能更容易理解)。

我不会编写游戏代码,也不知道游戏循环的典型特征是什么,但这种方法看起来很难测试。我会尝试将其分解为几个步骤,其中每个步骤/命令都可以轻松地单独测试。一种选择是为每个命令定义一个方法并使用send. 这将允许您测试每个命令是否独立于您的输入解析和游戏循环本身。

  COMMANDS = {
    q: :quit,
    # etc..
  }.stringify_keys.freeze

  def play_round # Ruby methods should be snake_case rather than camelCase
    @board.print_board
    run_command(gets.chomp)
    play_round unless @quitted || @board.board_complete
  end

  def run_command(input)
    command = parse_input_to_command(input)
    run_command(command)
  end

  def parse_input_to_command(input)
    COMMANDS[input] || :bad_command
  end
  def run_command(command)
    send("run_#{command}")
  end
  # Then a method for each command, e.g.
  def run_bad_input
    puts "Input not recognized"
  end

但是,对于这种类型的问题,我真的很喜欢函数式方法,其中每个命令只是一个无状态函数,您可以将状态传递到其中并获取新状态。这些可以改变它们的输入状态(eww)或返回具有更新状态的板的新副本(耶!)。就像是:

  COMMANDS = {
    # All state change must be done on board. To be a functional pattern, you should not mutate the board but return a new one. For this I invent a `.copy()` method that takes attributes to update as input.
    q: -> {|board| board.copy(quitted: true) },
    h: -> HintGiver.new, # If these commands were complex, they could live in a separate class entirely.
    bad_command: -> {|board| puts "Unrecognized command"; board },
    #
  }.stringify_keys.freeze
  def play_round 
    @board.print_board
    command = parse_input_to_command(gets.chomp)
    @board = command.call(@board)
    play_round unless @board.quitted || @board.board_complete
  end

  def parse_input_to_command(input)
    COMMANDS[input] || COMMANDS[:bad_command]
  end
于 2021-09-20T23:40:36.520 回答