如何使用rspec测试CLI的stdin

我正在制作一个小型的Ruby程序,无法弄清楚如何编写模拟多个用户命令行输入的RSpec规范(function本身可行)。 我认为这个StackOverflow的答案可能涵盖了最接近我的地方,但这并不是我需要的。 我使用Thor作为命令行界面,但我不认为这是Thor的任何问题。

程序可以从文件或命令行读取命令,并且我已经能够成功编写测试以读取执行它们。 这是一些代码:

cli.rb

class CLI  " while line = gets break if line =~ /EXIT/i yield [line] print "> " end end end # .. end 

我已经成功测试了使用以下代码执行文件中包含的命令:

投机/ cli_spec.rb

 describe CLI do let(:cli) { CLI.new } subject { cli } describe "executing instructions from a file" do let(:default_file) { "instructions.txt" } let(:output) { capture(:stdout) { cli.execute } } context "containing valid test data" do valid_test_data.each do |data| expected_output = data[:output] it "should parse the file contents and output a result" do cli.stub(:options) { { filename: default_file } } # Thor options hash File.stub(:readlines).with(default_file) do StringIO.new(data[:input]).map { |a| a.strip.chomp } end output.should == expected_output end end end end # ... end 

并且valid_test_data提到的valid_test_data采用以下forms:

支持/ utilities.rb

 def valid_test_data [ { input: "C1 ARGS\r C2\r C3\r C4", output: "OUTPUT\n" } # ... ] end 

我现在要做的是完全相同的事情,但不是从’文件’读取每个命令并执行它,我想以某种方式模拟用户输入到stdin 。 下面的代码是完全错误的 ,但我希望它可以传达我想要的方向。

投机/ cli_spec.rb

 # ... # !!This code is wrong and doesn't work and needs rewriting!! describe "executing instructions from the command line" do let(:output) { capture(:stdout) { cli.execute } } context "with valid commands" do valid_test_data.each do |data| let(:expected_output) { data[:output] } let(:commands) { StringIO.new(data[:input]).map { |a| a.strip } } it "should process the commands and output the results" do commands.each do |command| cli.stub!(:gets) { command } if command == :report STDOUT.should_receive(:puts).with(expected_output) else STDOUT.should_receive(:puts).with("> ") end end output.should include(expected_output) end end end end 

我尝试在各个地方使用cli.stub(:puts) ,并且通常重新安排这些代码,但似乎无法获取我的任何存根来将数据放入stdin。 我不知道我是否可以像命令文件一样解析命令行所期望的输入集,或者我应该用什么代码结构来解决这个问题。 如果指定了命令行应用程序的人可以插入,那就太棒了。 谢谢。

我认为一些间接可以帮助你为这段代码编写一个unit testing,而不是对宇宙进行存根。 您可以做的最简单的事情是允许注入输出的IO对象,并将其默认为STDOUT

 class CLI < Thor def initialize(stdout=STDOUT) @stdout = stdout end # ... @stdout.puts # instead of raw puts end 

然后,在测试中,您可以使用StringIO来检查打印的内容:

 let(:stdout) { StringIO.new } let(:cli) { CLI.new(stdout) } 

另一个选择是使用Aruba或类似的东西,并编写完整的集成测试,您实际运行您的程序。 这也有其他挑战(例如非破坏性并确保不会挤压/使用系统或用户文件),但对您的应用程序将是一个更好的测试。

Aruba是Cucumber,但断言可以使用RSPec匹配器。 或者,你可以使用Aruba的Ruby API(没有文档,但是公开并且效果很好)来做到这一点,而不需要Gherkin的麻烦。

无论哪种方式,您都将受益于使您的代码更适合测试。

我最终找到了一个解决方案,我认为这个解决方案非常接近于从文件执行指令的代码。 我克服了主要的障碍,最终意识到我可以编写cli.stub(:gets).and_return并将其传递给我想要执行的命令数组(作为参数感谢splat *运算符),然后传递给它"EXIT"命令完成。 如此简单,却如此难以捉摸。 非常感谢StackOverflow这个问题和答案让我超越了这条线。

这是代码:

 describe "executing instructions from the command line" do let(:output) { capture(:stdout) { cli.execute } } context "with valid commands" do valid_test_data.each do |data| let(:expected_output) { data[:output] } let(:commands) { StringIO.new(data[:input]).map { |a| a.strip } } it "should process the commands and output the results" do cli.stub(:gets).and_return(*commands, "EXIT") output.should include(expected_output) end end end # ... end 

你看过Aruba吗? 它允许您为命令行程序编写Cucumberfunction测试。 您可以通过这种方式定义CLI的输入。

您可以使用RSpec编写function定义,因此它不是全新的。

您可以使用allow_any_instance_ofallow_any_instance_of存根所有Thor::Actions

这是一个例子:

 it "should import list" do allow_any_instance_of(Thor::Actions).to receive(:yes?).and_return(true) end