【问题标题】:Testing a REPL in Ruby with RSpec and threads使用 RSpec 和线程在 Ruby 中测试 REPL
【发布时间】:2013-04-29 03:26:26
【问题描述】:

我正在使用 RSpec 来测试一个简单的 REPL 的行为。 REPL 只是回显输入的内容,除非输入是“退出”,在这种情况下它会终止循环。

为避免挂起测试运行程序,我在单独的线程中运行 REPL 方法。为了确保线程中的代码在我写关于它的期望之前已经执行,我发现有必要包含一个简短的sleep 调用。如果我删除它,测试会间歇性地失败,因为有时会在线程中的代码运行之前做出预期。

什么是构建代码和规范的好方法,这样我就可以确定地对 REPL 的行为做出预期,而无需 sleep hack?

这是 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

【问题讨论】:

    标签: ruby multithreading rspec read-eval-print-loop


    【解决方案1】:

    您可以通过队列支持它,而不是让 :stdout 成为 StringIO。然后,当您尝试从队列中读取数据时,您的测试将一直等到 REPL 将某些内容推送到队列中(也就是写入标准输出)。

    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 }
    

    我只是写了这个,没有尝试过,它可能不足以满足您的需求,但它明白了重点。 如果你像这样使用数据结构来同步两个线程,那么你根本不需要休眠。由于这消除了不确定性,你应该不会看到间歇性故障。

    【讨论】:

    • 这给我带来了很长的路要走,但不幸的是我仍然有间歇性故障(在一个示例中仍然需要睡眠调用),因为我不能保证 MRI 实际上切换回运行在 RSpec 示例中做出期望之前循环。
    【解决方案2】:

    我在这种情况下使用了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
    

    【讨论】:

      猜你喜欢
      • 2018-02-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-10-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多