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
我在使用 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