2013-04-29 54 views
2

我正在使用RSpec來測試簡單REPL的行爲。除非輸入是「退出」,否則REPL只是迴應輸入的內容,在這種情況下,它會終止循環。使用RSpec和線程測試Ruby中的REPL

爲了避免懸掛測試跑步者,我在單獨的線程內運行REPL方法。爲了確保線程中的代碼在我寫預期之前已經執行完畢,我發現有必要包含一個簡短的sleep調用。如果我刪除它,測試會間歇性失敗,因爲有時會在線程中的代碼運行之前做出期望。

什麼是構造代碼和規範的好方法,以便我可以確定性地對REPL的行爲進行期望,而不需要sleep黑客?

下面是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 

回答

1

與其讓:標準輸出是一個StringIO的,你可以通過一個隊列備份它。然後,當你試圖從隊列中讀取數據時,你的測試只是等到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 } 

我只是寫了這個沒有嘗試出來,它可能不足以滿足您的需要,但它得到了重點。 如果你使用數據結構來同步這兩個線程,那麼你根本不需要睡覺。由於這消除了非確定性,所以不應該看到間歇性故障。

+0

這給我帶來了很長的路,但不幸的是我仍然有間歇性故障(並且在一個例子中仍然需要睡眠呼叫),因爲我不能保證MRI在預期之前切換回到運行循環的線程以RSpec爲例。 – 2013-05-01 08:03:18

0

我已經使用了running?後衛這樣的情況。你可能無法完全避免睡眠,但你可以避免不必要的睡眠。

首先,將running?方法添加到您的REPL類中。

class REPL 
    ... 

    def running? 
    [email protected] 
    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