28

オブジェクトに対して長時間実行される可能性のある計算を実行する非同期タスクがあります。結果はオブジェクトにキャッシュされます。複数のタスクが同じ作業を繰り返さないようにするために、アトミック SQL 更新によるロックを追加しました。

UPDATE objects SET locked = 1 WHERE id = 1234 AND locked = 0

ロックは非同期タスク専用です。オブジェクト自体は、ユーザーによって更新される可能性があります。その場合、オブジェクトの古いバージョンの未完了のタスクは、古い可能性があるため、その結果を破棄する必要があります。これも、アトミック SQL 更新を使用すると非常に簡単に実行できます。

UPDATE objects SET results = '...' WHERE id = 1234 AND version = 1

オブジェクトが更新されている場合、そのバージョンは一致しないため、結果は破棄されます。

これら 2 つのアトミックな更新は、考えられる競合状態を処理する必要があります。問題は、単体テストでそれを確認する方法です。

最初のセマフォは、(1) オブジェクトがロックされている場合と (2) オブジェクトがロックされていない場合の 2 つのシナリオで 2 つの異なるテストをセットアップするだけなので、簡単にテストできます。(SQL クエリの原子性をテストする必要はありません。それはデータベース ベンダーの責任です。)

2 番目のセマフォをどのようにテストしますか? オブジェクトは、最初のセマフォの後、2 番目のセマフォの前に、第三者によって変更される必要があります。これには、更新が確実かつ一貫して実行されるように実行を一時停止する必要がありますが、RSpec でブレークポイントを挿入するサポートがないことを私は知っています。これを行う方法はありますか?または、そのような競合状態をシミュレートするために見落としている他の手法はありますか?

4

1 に答える 1

28

電子機器製造からアイデアを借りて、テスト フックを製品コードに直接組み込むことができます。回路を制御およびプローブするためのテスト機器用の特別な場所を備えた回路基板を製造できるのと同じように、コードでも同じことができます。

データベースに行を挿入するコードがあるとします。

class TestSubject

  def insert_unless_exists
    if !row_exists?
      insert_row
    end
  end

end

しかし、このコードは複数のコンピューターで実行されています。別のプロセスがテストと挿入の間に行を挿入し、DuplicateKey 例外が発生する可能性があるため、競合状態が発生します。コードがその競合状態から生じる例外を処理することをテストしたいと思います。row_exists?これを行うには、テストで への呼び出しの後、 への呼び出しの前に行を挿入する必要がありますinsert_row。そこで、そこにテストフックを追加しましょう:

class TestSubject

  def insert_unless_exists
    if !row_exists?
      before_insert_row_hook
      insert_row
    end
  end

  def before_insert_row_hook
  end

end

野生で実行されると、フックは CPU 時間をわずかに食い尽くすだけで何もしません。しかし、コードが競合状態についてテストされている場合、テストは before_insert_row_hook にモンキー パッチを適用します。

class TestSubject
  def before_insert_row_hook
    insert_row
  end
end

ずるいじゃない?疑いを持たないイモムシの体を乗っ取った寄生バチの幼虫のように、テストはテスト対象のコードを乗っ取って、テストが必要な正確な状態を作成します。

このアイデアは XOR カーソルと同じくらい単純なので、多くのプログラマーが独自に考案したものだと思います。一般に、競合状態のあるコードをテストするのに役立つことがわかりました。お役に立てば幸いです。

于 2010-01-10T05:30:15.673 に答える