2

単純な REPL の動作をテストするために RSpec を使用しています。REPL は、入力が「終了」でない限り、入力が何であれエコー バックします。その場合、ループは終了します。

テスト ランナーがハングしないようにするために、別のスレッド内で REPL メソッドを実行しています。sleepスレッド内のコードが実行されたことを確認してから、それについて期待することを書くために、簡単な呼び出しを含める必要があることがわかりました。これを削除すると、スレッド内のコードが実行される前に予期が行われることがあるため、テストが断続的に失敗します。

sleepハックを必要とせずに、REPL の動作について決定論的に期待できるように、コードと仕様を構成する良い方法は何ですか?

REPL クラスと仕様は次のとおりです。

class REPL
  def initialize(stdin = $stdin, stdout = $stdout)
    @stdin = stdin
    @stdout = stdout
  end

  def run
    @stdout.puts "Type exit to end the session."

    loop do
      @stdout.print "$ "
      input = @stdin.gets.to_s.chomp.strip
      break if input == "exit"
      @stdout.puts(input)
    end
  end
end

describe REPL do
  let(:stdin) { StringIO.new }
  let(:stdout) { StringIO.new }
  let!(:thread) { Thread.new { subject.run } }

  subject { described_class.new(stdin, stdout) }

  # Removing this before hook causes the examples to fail intermittently
  before { sleep 0.01 }

  after { thread.kill if thread.alive? }

  it "prints a message on how to end the session" do
    expect(stdout.string).to match(/end the session/)
  end

  it "prints a prompt for user input" do
    expect(stdout.string).to match(/\$ /)
  end

  it "echoes input" do
    stdin.puts("foo")
    stdin.rewind
    expect(stdout.string).to match(/foo/)
  end
end
4

2 に答える 2

1

:stdout を StringIO にする代わりに、Queue でバックアップできます。その後、キューから読み取ろうとすると、テストは REPL が何かをキューにプッシュする (別名、stdout に書き込む) まで待機します。

require 'thread'

class QueueIO
  def initialize
    @queue = Queue.new
  end

  def write(str)
    @queue.push(str)
  end

  def puts(str)
    write(str + "\n")
  end

  def read
    @queue.pop
  end
end

let(:stdout) { QueueIO.new }

私はこれを試してみずに書いただけで、あなたのニーズに対して十分に堅牢ではないかもしれませんが、それは要点を理解しています. このようにデータ構造を使用して 2 つのスレッドを同期する場合、スリープする必要はまったくありません。これにより非決定論が取り除かれるため、断続的な障害は発生しません。

于 2013-04-30T02:51:13.530 に答える
0

私はrunning?このような状況のためにガードを使用しました。睡眠を完全に避けることはできないかもしれませんが、不必要な睡眠を避けることはできます。

まず、running?REPL クラスにメソッドを追加します。

class REPL
  ...

  def running?
    !!@running
  end

  def run
    @running=true

    loop do
      ...
      if input == 'exit
        @running = false
        break
      end
      ...
    end
  end
end

次に、仕様で、REPL が実行されるまでスリープします。

describe REPL do
  ...
  before { sleep 0.01 until REPL.running? }
  ...
end
于 2013-04-29T04:42:36.577 に答える