如何测试标准输入一个命令行使用的RSpec(How to test stdin for a CLI

2019-09-23 06:52发布

我正在做一个小Ruby程序并不能弄清楚如何编写模拟多个用户的命令行输入(作品本身的功能)的RSpec规格。 我觉得这个StackOverflow的答案可能包括地面最接近我在哪里,但它并不完全符合我的需要。 我使用雷神的命令行界面,但我不认为这是在托尔任何问题。

该程序可以在命令或者从文件或命令行读取,我已经能够成功地编写测试在执行他们阅读。 下面是一些代码:

cli.rb

class CLI < Thor
  # ...
  method_option :filename, aliases: ['-f'],
                desc: "name of the file containing instructions",
                banner: 'FILE'

  desc "execute commands", "takes actions as per commands"
  def execute
    thing = Thing.new
    instruction_set do |instructions|
      instructions.each do |instruction|
        command, args = parse_instruction(instruction) # private helper method
        if valid_command?(command, args) # private helper method
          response = thing.send(command, *args)
          puts format(response) if response
        end
      end
    end
  end

  # ...

  no_tasks do
    def instruction_set
      if options[:filename]
        yield File.readlines(options[:filename]).map { |a| a.chomp }
      else
        puts usage
        print "> "
        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以上提及是以下形式:

支持/ 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)在不同的地方,一般清理周围有很多这样的代码,但似乎无法得到任何我的存根把数据标准输入。 我不知道我是否可以解析的命令行中输入集我期望在相同的方式,我用命令的文件,或者什么样的代码结构,我应该使用来解决这个问题做。 如果有人谁SPEC-ED了命令行应用程序可以附和,那将是巨大的。 谢谢。

Answer 1:

而不是磕碰宇宙,我想间接的几位会帮助你编写的代码单元测试。 你可以做最简单的事情是让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) }

另一种选择是使用阿鲁巴或类似的东西,写全方位的集成测试,在那里你实际运行您的程序。 这有其他的挑战,以及(如被无损,并确保不要压扁/使用的系统或用户文件),但将您的应用程序的一个更好的测试。

阿鲁巴是黄瓜,但是断言可以使用RSPEC的匹配。 或者,你可以使用Aruba的红宝石API(这是无证的,但公众和伟大工程)做到不小黄瓜的麻烦。

无论哪种方式,你将受益于使你的代码更测试友好一点。



Answer 2:

我最终找到一个解决方案,我觉得很贴切地反映了从文件中执行指令代码。 我通过最终意识到,我可以写克服了主要障碍cli.stub(:gets).and_return ,我想执行命令的数组中传递它(为感谢参数的图示*运营商),然后将它传递了"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


Answer 3:

你看阿鲁巴 ? 它可以让你写的命令行程序黄瓜功能测试。 您可以定义输入到你的CLI的方式。

你可以写你的特性定义使用RSpec,所以它不是全新的。



Answer 4:

您可以存根所有Thor::Actions使用RSpec的allow_any_instance_of

这里是一个例子:

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


文章来源: How to test stdin for a CLI using rspec