带有控制台输入和输出的RSpec循环测试



我有一个输入方法,用于从控制台读取圆半径。若输入无效,方法输出错误消息并循环以再次读取输入。

因此,我需要进行一个rspec测试,通过无效输入数组进行迭代,并期望input方法每次都会向控制台输出错误消息。

这是我的输入类:

# frozen_string_literal: true
require_relative '../data/messages'
# Input class is responsible for reading and writing to and from console and
# querying user
class Input
def read
loop do
print "#{RADIUS_QUERY_MSG}n> "
radius = gets.strip
return radius.to_f if valid?(radius)
puts INVALID_MSG
end
end
private
def valid?(radius)
/A[+]?d+(.d+)?z/.match(radius)
end
end

我在rspec测试中尝试过,但它似乎进入了某种无限循环:

# frozen_string_literal: true
require 'input'
require_relative '../data/messages'
require_relative '../data/rspec'
RSpec.describe Input do
let(:input) { described_class.new }
describe '#read' do
INVALID_INPUTS.each do |invalid_input|
context "with invalid input "#{invalid_input}"" do
it 'tells user that input is invalid' do
allow(input).to receive(:gets).and_return(invalid_input)
expect(input.read).to output("#{INVALID_MSG}n").to_stdout
end
end
end
end
end

我怎样才能正确地做到这一点?如果有任何帮助,我将不胜感激。

p.S.找到了这篇文章,但对我没有用。也许它会有所帮助。https://haughtcodeworks.com/blog/software-development/easy-loop-testing/

p.p.S.INVALID_MSGRADIUS_QUERY_MSG是字符串,INVALID_INPUTS是字符串数组。

将测试输入注入";真实的";方法

对于不是先写测试的代码来说,这是一个常见的问题。有几种方法可以解决这个问题,但最简单的选择是不要嘲笑、打断或以其他方式使你的";真实的";代码只是重构方法本身。例如:

def read test_input: nil
loop do
print "#{RADIUS_QUERY_MSG}n> "
radius = (test_input || gets).strip
return radius.to_f if valid?(radius)
puts INVALID_MSG
end
end

现在,您可以简单地将您想要的任何值注入RSpec测试中的可选test_input关键字参数中,以确保输入被正确剥离,并显示您正在测试的任何其他行为。

这避免了您在编写一个难以测试的方法时可能遇到的各种问题。要么直接向方法提供测试输入,在这种情况下方法会使用它,要么不提供,在这种情形下它会像往常一样调用#gets。

记住,目标不是测试#gets这样的核心方法。相反,您应该测试给定特定输入或状态的方法或对象的行为。如果你允许在代码中注入依赖项,或者重构类以允许在测试设置中修改实例变量,并使用这些变量而不是传递给方法的方法参数,从而使方法可测试,那么你就可以确保你在测试真实的类或方法,而不是绕过它

当然还有其他更复杂的方法来做我上面做的事情,但对于这个特定的例子来说,它们似乎没有必要。KISS原则绝对适用!

最新更新