Ruby 如何rspec线程化代码?

Ruby 如何rspec线程化代码?,ruby,multithreading,rspec,Ruby,Multithreading,Rspec,开始使用rspec时,我很难测试线程代码。 这里是一个代码的简化版本,我之所以这样做是因为我需要一个具有超时功能的队列 require "thread" class TimeoutQueue def initialize @lock = Mutex.new @items = [] @new_item = ConditionVariable.new end def push(obj) @lock.synchronize do



开始使用rspec时,我很难测试线程代码。 这里是一个代码的简化版本,我之所以这样做是因为我需要一个具有超时功能的队列

require "thread"

class TimeoutQueue
   def initialize
      @lock = Mutex.new
      @items = []
      @new_item = ConditionVariable.new
   end

   def push(obj)
      @lock.synchronize do
         @items.push(obj)
         @new_item.signal
      end
   end

   def pop(timeout = :never)
      timeout += Time.now unless timeout == :never
      @lock.synchronize do
         loop do
            time_left = timeout == :never ? nil : timeout - Time.now
            if @items.empty? and time_left.to_f >= 0
               @new_item.wait(@lock, time_left)
            end
            return @items.shift unless @items.empty?
            next if timeout == :never or timeout > Time.now
            return nil
         end
      end
   end

   alias_method :<<, :push
end
需要“线程”
类超时队列
def初始化
@lock=Mutex.new
@项目=[]
@new\u item=ConditionVariable.new
结束
def推送(obj)
@同步锁
@物品推送(obj)
@新项目信号
结束
结束
def pop(超时=:从不)
timeout+=时间。除非timeout==否则立即:从不
@同步锁
环道
剩余时间=超时=:从不?零:超时时间。现在
如果@items.empty?和时间_left.to _f>=0
@新建项目。等待(@lock,剩余时间)
结束
返回@items.shift,除非@items.empty?
下一个if timeout==:从不或timeout>Time.now
归零
结束
结束
结束

alias_method:当单元测试时,我们不希望任何不确定的行为影响我们的测试,所以当测试线程时,我们不应该并行运行任何东西

相反,我们应该隔离我们的代码,并通过存根
@lock
@new\u item
,甚至可能是
时间来模拟我们想要测试的情况。现在
(为了更具可读性,我冒昧地想象您还有
attr\u reader:lock,:new\u item
):


等等。

好的,再次感谢;)因此,如果我得到了正确的结果,我将不会在一个线程上测试填充队列并在另一个线程上接收值。我会尽量记住这一点,但不知何故,当我开发一个可以读取输入的命令行接口时,它很糟糕。。。这将是一件很难测试的事情:/你应该模拟所有的用例。模拟一个
@items
为空的情况,期望调用
wait
,将一个项目放在
@items
中,并查看它是否返回……好的,所以我要做的是为每个用例解构每个流程,并通过“模拟另一个流程”单独测试它,但我不测试并行运行的两个流程。希望我真的得到了;)你的解释有很多,说你永远不应该并行测试多线程代码是愚蠢的。显然,以单线程的方式测试程序的逻辑是很好的,因为这样更清楚,更容易推理。但是,如果您使用的是线程,请向非决定论问好!您可以而且应该测试非确定性代码,尽管有一些警告。如果您没有任何使用多线程运行的集成测试,那么您就不是在测试真正的程序。@AndrewCholakian——我没有说“永远不要测试多线程代码”——这确实很愚蠢。我说过你不应该以非确定性的方式进行单元测试!单元测试是关于对代码的信心。如果您的单元测试有时失败-当它成功时,您什么都不知道-它可能会在下次失败。。。相反,您应该以确定性的方式模拟非确定性行为(这样它总是会失败),这样您就知道您没有破坏代码。当然,集成/系统/压力测试等应在实际条件下运行,尽可能接近prod。
it 'should signal after push' do
  allow(subject.lock).to receive(:synchronize).and_yield

  expect(subject.new_item).to receive(:signal)

  subject.push('object')

  expect(subject.items).to include('object')
end

it 'should time out if taken to long to enter synchronize loop' do
  @now = Time.now
  allow(Time).to receive(:now).and_return(@now, @now + 10.seconds)
  allow(subject.items).to receive(:empty?).and_return true
  allow(subject.lock).to receive(:synchronize).and_yield

  expect(subject.new_item).to_not receive(:wait)

  expect(subject.pop(5.seconds)).to be_nil
end