2

以下にリストされている仕様の実行中にエラーが発生しました。スレッドが完了するのを待たず、migrate メソッドのスタブを解除し、その結果スレッドの 1 つが実際のメソッドにヒットします。
レールがロードされている場合にのみ発生することに気付きました.レールがなくても正しく動作するか、単に速く終了します...

仕様:

it "should not allow infinit recursion" do
  runner.stub(total_records: 4)
  runner.stub(:fetch_records_batch).and_return([:one, :two])
  runner.should_receive(:migrate).at_most(100).times
  expect { runner.run }.to raise_error(Exception, /Migration fall into recursion/)
end

it "should pass"
  1.should eq 1
end

抽出されたコード:

class Runner
  def migrate(record)
    raise NotImplementedError
  end

  def run
     while have_records?(records = fetch_records_batch)
       threads = []
       records.each do |record|
         threads << Thread.new(record) do |thread_record|
           begin
             result = migrate(thread_record)
           rescue RuntimeError => exception
             register_event :record_migration_error, thread_record, exception
           end
         end
         recursion_preventer
      end
      threads.each(&:join)
    end
  end

  def recursion_preventer
    @counter ||= 0
    @counter += 1
    raise Exception, "Migration fall into recursion. Check logs." if @counter > (total_records * 1.1).round + 10
  end
end
4

2 に答える 2

4

Thread クラスをいつでもモックして、スレッドを開始せずに代わりにコードを実行できます。これは、提案された方法でテストを書き直すよりもはるかに邪魔にならないことがわかります。

これをテストクラスに追加します。

describe MyClass do
  before(:each) do
    allow(Thread).to receive(:new).and_yield
  end
  #Your normal unaltered tests under here
end

この方法の落とし穴は、競合状態が発生しないため、「スレッド a とスレッド b が同時に同じ変数に書き込み、問題が発生する」などのバグがコードに含まれる可能性があることです。他のスレッドに依存して何かを完了するコードは、このアプローチでロックアップする可能性が高いため、デッドロックのシナリオも考慮する必要があります。

于 2014-10-22T11:29:47.877 に答える
1

ensure ブロックを追加して、threads.each(&:join) を呼び出し、recursion_preventer を records.each の直後に移動することで解決しました。

于 2013-04-19T10:35:26.263 に答える