4

私は小さな Ruby プログラムを作成していますが、複数のユーザー コマンド ライン入力をシミュレートする RSpec 仕様を記述する方法がわかりません (機能自体は機能します)。この StackOverflow の回答は、おそらく私がいる場所に最も近い場所をカバーしていると思いますが、それは私が必要としているものではありません。コマンド ライン インターフェースにThorを使用していますが、これは Thor の問題ではないと思います。

このプログラムは、ファイルまたはコマンド ラインからコマンドを読み込むことができます。テストを正常に記述して、それらを読み込んで実行することができました。ここにいくつかのコードがあります:

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)さまざまな場所で使用しようとしましたが、一般的にこのコードを何度も再配置しましたが、データを標準入力に入れるためのスタブを取得できないようです。コマンドのファイルで行うのと同じ方法で、コマンドラインから期待される一連の入力を解析できるかどうか、またはこの問題を解決するためにどのコード構造を使用する必要があるかはわかりません。コマンドライン アプリの仕様を決めた人が参加できれば、それは素晴らしいことです。ありがとう。

4

4 に答える 4

8

ユニバースをスタブ化するのではなく、いくつかの間接化を行うと、このコードの単体テストを作成するのに役立つと思います。あなたができる最も簡単なことはIO、出力のためのオブジェクトが注入されることを許可し、それをデフォルトにすることですSTDOUT:

class CLI < Thor
  def initialize(stdout=STDOUT)
    @stdout = stdout
  end

  # ...

  @stdout.puts # instead of raw puts
end

次に、テストで a を使用して、StringIO出力された内容を調べることができます。

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

もう 1 つのオプションは、Aruba などを使用して、実際にプログラムを実行する完全な統合テストを作成することです。これには他にも課題があります (非破壊的であること、システムまたはユーザー ファイルを押しつぶしたり使用したりしないようにすることなど)、アプリのより良いテストになります。

Aruba は Cucumber ですが、アサーションでは RSPec マッチャーを使用できます。または、Aruba の Ruby API (文書化されていませんが公開されており、うまく機能します) を使用して、Gherkin の手間をかけずにこれを行うことができます。

いずれにせよ、コードをもう少しテストしやすいものにすることでメリットが得られます。

于 2012-10-05T12:51:04.837 に答える
2

最終的に、ファイルから命令を実行するためのコードをかなり厳密に反映していると思われる解決策を見つけました。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
于 2012-10-06T13:20:31.983 に答える
1

アルバを見たことがありますか?コマンド ライン プログラム用の Cucumber 機能テストを作成できます。その方法で CLI への入力を定義できます。

機能定義は RSpec で記述できるため、まったく新しいものではありません。

于 2012-10-01T12:48:46.257 に答える