如何在RSpec测试中自动为gets方法提供输入以避免手动操作?
如何自动化测试依赖用户输入的Ruby类方法?
问题场景
我有一个处理用户输入的Ruby类方法,会循环请求输入直到获得非0正整数:
class Input # Method to get a valid number of days to assign prices to def Input.get_valid_number_of_days puts "Time to enter the number of days to be considered.\nUse positive integers only" # Loop requesting for a positive integer different than 0 loop do print "How many days do you want to assign prices to? " days_number = gets.chomp # Readability improvement isnt_digit_only = /\D/.match?(days_number) return days_number.to_i if not isnt_digit_only and days_number != "0" # In case input is invalid puts "This is not a positive integer different from 0." end end end
主程序通过number_of_days = Input.get_valid_number_of_days调用该方法。我尝试用RSpec自动化测试,但运行测试时出现错误:
Time to enter the number of days to be considered. Use positive integers only How many days do you want to assign prices to? An error occurred while loading ./spec/stock_picker_spec.rb. Failure/Error: days_number = gets.chomp Errno::ENOENT: No such file or directory @ rb_sysopen - --format # ./lib/stock_picker.rb:12:in `gets' # ./lib/stock_picker.rb:12:in `gets' # ./lib/stock_picker.rb:12:in `block in get_valid_number_of_days' # ./lib/stock_picker.rb:10:in `loop' # ./lib/stock_picker.rb:10:in `get_valid_number_of_days' # ./lib/stock_picker.rb:94:in `<top (required)>' # ./spec/stock_picker_spec.rb:2:in `<top (required)>' No examples found. Finished in 0.00008 seconds (files took 0.19796 seconds to load) 0 examples, 0 failures, 1 error occurred outside of examples
我的测试代码如下:
require "./lib/stock_picker" # Test input related methods describe Input do describe ".get_valid_number_of_days" do it "Return a positive integer different than 0 when valid input" do allow($stdin).to receive(:gets).and_return("3\n") days = $stdin.gets expect(days).to be_kind_of(Integer) end end end
解决方案
1. 修复文件加载时的自动执行问题
报错核心原因:你的lib/stock_picker.rb在加载阶段(第94行)直接调用了Input.get_valid_number_of_days,导致RSpec启动时就触发了交互逻辑。此时测试用例的Mock还未生效,gets会读取RSpec的命令行参数(如--format),从而抛出错误。
解决方法:将主程序逻辑封装到入口方法中,仅当直接运行脚本时才执行:
# lib/stock_picker.rb class Input # ... 你的类方法代码 ... end # 封装主程序逻辑 def run_program number_of_days = Input.get_valid_number_of_days # 其他业务逻辑 end # 只有直接运行该文件时才执行主程序 if __FILE__ == $0 run_program end
2. 修正RSpec测试逻辑
你的测试用例没有调用目标方法,只是单独读取了$stdin,完全没覆盖到Input.get_valid_number_of_days的逻辑。修正后的测试代码:
require "./lib/stock_picker" describe Input do describe ".get_valid_number_of_days" do it "返回非0正整数当输入有效时" do # Mock $stdin的gets方法,返回模拟输入 allow($stdin).to receive(:gets).and_return("3\n") # 调用要测试的类方法 result = Input.get_valid_number_of_days # 断言结果符合要求 expect(result).to be_a(Integer) expect(result).to eq(3) expect(result).to be > 0 end it "拒绝非数字输入并重新请求" do # 先返回无效输入,再返回有效输入 allow($stdin).to receive(:gets).and_return("abc\n", "2\n") # 验证错误提示是否输出 expect { Input.get_valid_number_of_days }.to output( /This is not a positive integer different from 0./ ).to_stdout result = Input.get_valid_number_of_days expect(result).to eq(2) end it "拒绝0输入并重新请求" do allow($stdin).to receive(:gets).and_return("0\n", "5\n") expect { Input.get_valid_number_of_days }.to output( /This is not a positive integer different from 0./ ).to_stdout result = Input.get_valid_number_of_days expect(result).to eq(5) end end end
3. 可选:隔离输入输出依赖(更优实践)
为了让代码更易测试,避免依赖全局变量$stdin和$stdout,可以将它们作为参数传入方法:
class Input def self.get_valid_number_of_days(input: $stdin, output: $stdout) output.puts "Time to enter the number of days to be considered.\nUse positive integers only" loop do output.print "How many days do you want to assign prices to? " days_number = input.gets.chomp isnt_digit_only = /\D/.match?(days_number) return days_number.to_i unless isnt_digit_only || days_number == "0" output.puts "This is not a positive integer different from 0." end end end
测试时用StringIO模拟输入输出,无需Mock全局变量:
require "stringio" require "./lib/stock_picker" describe Input do describe ".get_valid_number_of_days" do it "处理有效输入并返回正确结果" do input = StringIO.new("3\n") output = StringIO.new result = Input.get_valid_number_of_days(input: input, output: output) expect(result).to eq(3) expect(output.string).to include("How many days do you want to assign prices to? ") end it "处理无效输入并输出提示" do input = StringIO.new("abc\n0\n4\n") output = StringIO.new result = Input.get_valid_number_of_days(input: input, output: output) expect(result).to eq(4) expect(output.string).to include("This is not a positive integer different from 0.") end end end
内容的提问来源于stack exchange,提问作者Neblinus
相关产品推荐
相关产品推荐

