RSpec 使用控制台输入和输出进行循环测试
RSpec loop testing with console input and output
我有一个 input 方法,用于从控制台读取圆半径。如果输入无效,方法输出错误消息并循环再次读取输入。
所以我需要进行一个 rspec 测试,该测试通过无效输入数组进行迭代并期望
input方法每次都会输出错误到控制台消息。
这是我的输入 class:
# 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_MSG
和 RADIUS_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 这样的核心方法。相反,您应该测试给定特定输入或状态的方法或对象的 行为 。如果您通过允许在代码中进行依赖注入或重构 class 以允许在测试设置中修改实例变量并使用这些而不是传递给方法的方法参数来使方法可测试,则可以确保您正在测试您的方法真正的 class 或方法,而不是绕过它。
当然还有其他更复杂的方法可以完成我上面所做的事情,但对于这个特定示例来说它们似乎没有必要。 KISS 原则绝对适用!
我有一个 input 方法,用于从控制台读取圆半径。如果输入无效,方法输出错误消息并循环再次读取输入。
所以我需要进行一个 rspec 测试,该测试通过无效输入数组进行迭代并期望 input方法每次都会输出错误到控制台消息。
这是我的输入 class:
# 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_MSG
和 RADIUS_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 这样的核心方法。相反,您应该测试给定特定输入或状态的方法或对象的 行为 。如果您通过允许在代码中进行依赖注入或重构 class 以允许在测试设置中修改实例变量并使用这些而不是传递给方法的方法参数来使方法可测试,则可以确保您正在测试您的方法真正的 class 或方法,而不是绕过它。
当然还有其他更复杂的方法可以完成我上面所做的事情,但对于这个特定示例来说它们似乎没有必要。 KISS 原则绝对适用!