私は基本的に@Alexeiが言ったことをやることになりました。より具体的には、これは私がしたことです:
- 基本
PausingDictionary
的にすべてのメソッドのコールバックを持つだけの a を作成しますが、それ以外の場合は通常の辞書に渡すだけです
- テスト時に通常の Dictionary の代わりに PausingDictionary を使用できるように、コードを (DI などを使用して) 抽象化します。
- 単体テストに 2 つの ConcurrentDictionaries を追加しました。1 つは「Accessed」と呼ばれ、もう 1 つは「GoAhead」と呼ばれます。これの鍵はの組み合わせです
"action"+Thread.GetHashCode().ToString()
(アクションはコールバックごとに異なります)
- すべてをfalseに初期化し、いくつかの拡張メソッドを追加して、操作を少し簡単にしました
- 辞書のコールバックをセットアップして、スレッドの Accessed を true に設定しますが、GoAhead が true になるまでコールバックで待機します。
- 単体テスト内から 2 つのスレッドを開始しました。1 つのスレッドがディクショナリにアクセスします
GoAhead
が、そのスレッドでは が false であるため、そこに留まります。次に、2 番目のスレッドもディクショナリにアクセスしようとします。
Accessed
私のコードはそれをロックアウトする必要があるため、そのスレッドはfalseであるというアサーションがあります。
それだけではありません。IList もモックアップする必要がありますが、そうは思いません。これらの単体テストは価値がありますが、間違いなく世界で最も簡単に作成できるものではありません。セットアップ コードや偽のインターフェイスの実装などを除けば、各テストは約 25 行の非定型コードになります。ロックが難しい。ロックが効果的であることを証明することはさらに困難です。驚くべきことに、この種のパターンを使用すると、ほぼすべてのシナリオをテストできます。しかし、それは非常に冗長で、きれいなテストにはなりません
したがって、テストを書くのは大変ですが、これは完璧に機能します。ロックを削除すると一貫して失敗し、ロックを追加すると一貫して成功します。
編集:
スレッドの「インターリーブを制御する」この方法は、可能なインターリーブごとにテストを作成することを考えると、スレッドセーフをテストすることも可能になると思います。一部のコードではこれは不可能ですが、コードをロックするだけに限定されているわけではないと言いたいだけです。次のようなスレッドセーフな失敗を一貫して複製するために同じ方法を行うことができfoo.Contains(x)
ますvar tmp=foo[x]