我想用MiniTest测试下面的代码。代码测试用户是否提供了有效的输入。
正在测试的代码:
def player_input(min, max)
loop do
user_input = gets.chomp
verified_number = verify_input(min, max, user_input.to_i) if user_input.match?(/^\d+$/)
return verified_number if verified_number
puts "Input error! Please enter a number between #{min} or #{max}."
end
end
def verify_input(min, max, input)
return input if input.between?(min, max)
end
**MiniTest代码:**测试代码使用无效输入对:gets方法进行存根。然后Assertplayer_input方法的输出是错误消息。
def test_when_user_inputs_an_incorrect_value_runs_loop_once
invalid_input = '11'
@game_input.stub(:gets, invalid_input) do
min = @game_input.instance_variable_get(:@minimum)
max = @game_input.instance_variable_get(:@maximum)
error_message = "Input error! Please enter a number between #{min} or #{max}."
assert_output(error_message, "") { @game_input.player_input(min, max) }
end
end
**问题:**问题是因为我把测试在无限循环中运行的:gets方法截断了。如何在MiniTest中测试获取用户输入的简单循环?
谢谢
我尝试将代码更改为STDERR。puts以确保代码返回一个可以Assert的标准错误。
1条答案
按热度按时间ejk8hzay1#
未来为什么要先测试
这类问题就是为什么你应该首先编写测试。测试为测试而设计的代码要容易得多,而不是在事后改造代码或测试框架。从中吸取教训。
最简单的事情可能会起作用
不需要修复代码或测试,只要Assert如果由于任何原因陷入循环,就会从标准库的Timeout模块中获得Timeout::Error异常。例如:
假设这不是验证特定的错误消息,它解决了您在循环中面临的实际问题,而不需要更改现有的逻辑。如果你只是想停止在这个特定的测试中被挂起,而不在乎为什么,那么就走这条路。
让事情更简单
假设你想修复你的代码,而不仅仅是避免陷入循环,你应该简化你的代码。它只是看起来像一个天生的坏主意存根内核#获取,即使你做一个块内。MiniTest允许您将存根的范围限定为块,但有一种更简单的方法可以测试现有代码,而不会扭曲自己:只需添加一个方法参数,就可以让您在测试过程中做一些不同的事情!
你没有展示你是如何示例化你的类的,而且通常来说,为多个测试重用一个修改过的对象是一个坏主意,因为这会创建有状态的和顺序相关的测试。因此,您可以示例化类的一个新示例,然后在
ENV['APP_ENV'] == 'test'
和您传递了一些显式测试参数时,让方法执行不同的操作。考虑以下情况:接下来,如果您的测试环境没有为您设置
ENV['APP_ENV'] = 'test'
,请确保您的测试套件设置了ENV['APP_ENV'] = 'test'
。你可能也应该让GameInput公开测试变量的访问器,而不是尝试使用#instance_variable_get。最后,只需调用#player_input,并为测试值添加一个关键字参数。例如,考虑以下框架,您可能需要根据未共享的代码部分对其进行调整。是的,这需要一点代码重构,但从长远来看会更好。可能还有更好的重构方法,这样你就不会在本质上测试#loop或#gets in #player_input,因为你实际上是在测试#verify_input,它已经有了一个额外的可测试参数!
简化方法逻辑
您可以通过使用extract-method模式、将错误消息移动到验证器以及精简#player_input来大大简化代码。考虑这个替代方案,只要 @min 和 @max 大于零,它就可以在代码方面工作(不一定适用于现有的测试)。
这个逻辑在Ruby 3中很好用。2.2,简单的方法更容易测试,因为你基本上可以忽略#ask_for_number,因为你通常应该假设像#print和#这样的内置方法可以正常工作。如果他们不这样做,你有更大的问题。
这些较小的方法更容易测试和调试,也更容易推理。此外,使用Kernel#p而不是Kernel#puts(它总是返回nil)意味着你有一个更好的方法来测试你的输出作为一个返回值,而不是试图捕获输出。虽然调用
$stderr.puts msg
可能比调用p msg
更正确,但使用#p可以防止您必须在STDERR上Assert某些内容,而不仅仅是查看返回的String。您可能仍然需要在循环中做一些事情,例如添加额外的break或return语句,如果您希望在测试中接收特定值时提前退出。你的问题不是返回值,甚至不是循环本身;事实上,Ruby正在等待输入,当 not 正在测试时重新提示用户可能是正确的做法。毕竟,如果您只想在所有情况下运行一次,那么在循环中运行输入代码真的没有多大意义。