2013-04-29 56 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