RSpecのユニットテストで競合状態のシミュレーション
-
19-09-2019 - |
質問
我々は、オブジェクトのための潜在的に長期実行演算を実行する非同期タスクを有しています。その結果は、オブジェクトにキャッシュされます。同じ作業を繰り返すことから、複数のタスクを防ぐために、我々は、アトミックSQLの更新でロックを追加しました:
UPDATE objects SET locked = 1 WHERE id = 1234 AND locked = 0
ロッキングのみ非同期タスクのためのものです。オブジェクト自体は、まだユーザーによって更新することができます。その場合、彼らが古くなりそうだとして、オブジェクトの古いバージョンのため、未完了のタスクは、その結果を破棄しなければなりません。これは、アトミックSQLの更新をどうすることも非常に簡単です。
UPDATE objects SET results = '...' WHERE id = 1234 AND version = 1
オブジェクトが更新されている場合は、そのバージョンが一致しませんので、結果が破棄されます。
これらの二つの原子更新がすべての可能な競合状態を処理する必要があります。質問は、ユニットテストでそれを確認する方法である。
(1)オブジェクトがロックされ、(2)オブジェクトがロックされていない場合:それは単に2つのシナリオを持つ2回の異なる試験の設定の問題であるように、最初のセマフォは、テストが容易です。 (つまり、データベース・ベンダーの責任であるべきと私たちは、SQLクエリのアトミック性をテストする必要はありません。)
どのようにして第二のセマフォをテストするのですか?オブジェクトは、最初のセマフォの後が、2番目の前に、第三者によっていくつかの時間を変更する必要があります。更新が確実かつ一貫して実施することができるように、これは、実行中に一時停止を要求するだろうが、私はRSpecのでブレークポイントを注入するためのノーサポートを知っています。これを行う方法はありますか?それとも私は、このような競合状態をシミュレートするため見下ろすてる他のいくつかの技術があるのでしょうか?
解決
あなたは電子機器製造からアイデアを借りて生産コードに直接テストフックを置くことができます。回路基板は、回路を制御し、プローブする試験装置のための特別な場所で製造することができるのと同じように、私たちは、コードと同じことを行うことができます。
我々はデータベースに行を挿入するいくつかのコードを持っているとします。
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カーソルと同じくらい簡単ですので、私は多くのプログラマは、独立して、それを発明した疑い。私はそれがレースの条件でコードをテストするために有用であることが判明しています。私はそれが役に立てば幸います。