Ruby - 测试在 Minitest 中调用自身的方法

Ruby - Testing a method that calls itself in Minitest

我在使用 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
Error:
TestGame#test_playRound_a_input_triggers_getPlayerSet:
StandardError: StandardError
test_game.rb:136:in `test_playRound_a_input_triggers_getPlayerSet'

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

我对 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

-- 编辑--

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

我不会编写游戏代码,也不知道什么是典型的游戏循环,但这种方法看起来很难测试。我会尝试将其分解为几个步骤,其中每个 step/command 都可以轻松地单独测试。一个选择是为每个命令定义一个方法并使用 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) 或 return 具有更新状态的新板副本 (yay!)。类似于:

  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