2013-04-29 2 views
2

RSPL을 사용하여 간단한 REPL의 동작을 테스트하고 있습니다. REPL은 입력이 "exit"가 아닌 한, 입력이 무엇 이던지 에코를 반복합니다.이 경우 입력은 루프를 종료합니다.RSpec 및 스레드로 REPL 테스트하기

테스트 러너가 멈추지 않게하려면 별도의 스레드 안에 REPL 메소드를 실행하고 있습니다. 스레드에 대한 기대치를 쓰기 전에 스레드의 코드가 실행되었는지 확인하려면 간단한 sleep 호출을 포함시켜야합니다. 제거하면 테스트가 일시적으로 실패합니다. 왜냐하면 스레드의 코드가 실행되기 전에 때로는 기대가 이루어지기 때문입니다.

sleep 해킹없이 REPL의 행동을 결정 론적으로 기대할 수 있도록 코드와 스펙을 구조화하는 좋은 방법은 무엇입니까? 나는이 같은 상황에 대한 running? 가드를 사용했습니다

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 예제에서 만들어졌다. –

0

: 여기

는 REPL 클래스와 스펙입니다. 아마도 수면을 완전히 피할 수는 없지만 불필요한 수면을 피할 수는 있습니다.

먼저 REPL 클래스에 running? 메서드를 추가하십시오.

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