web-dev-qa-db-ja.com

Javaでスレッドセーフをユニットテストするための満足のいくアプローチはありますか?

入力が複数のワーカースレッド間で共有されている場合、スレッドセーフではないと思われるパッケージの改善を検討しています。 TDDの原則によれば、最初に失敗するテストをいくつか作成する必要があります。これらは、問題の評価に確かに役立ちます。

これは簡単に達成できることではなく、オペレーティングシステムがスケジューリングとさまざまな操作がインターリーブされる正確な順序を決定するため、単純にマルチスレッドテストは非決定的であることを理解しています。私は過去に MultithreadedTC を見て使用しましたが、これは役に立ちました。ただし、その場合、既存の実装がどこで失敗したかを事前に正確に知っていたため、それをカバーする一連の素晴らしいテストを作成することができました。

ただし、問題が何であるかを正確に把握していない場合は、潜在的な問題を投げかける可能性が高いテストを作成するための良い方法はありますか?他の人が役立つと思った図書館はありますか?純粋主義の観点から、マルチスレッドテストケースは通常のシングルスレッドテストと同じ呼び出しとアサーションであり、必要に応じて複数のワーカースレッドでのみ実行する必要があると考えるのは正しいでしょうか?

ツール/ベストプラクティス/哲学全般に関する提案は大歓迎です。

41
Andrzej Doyle

並行性の問題をテストして、良い結果を得るのを忘れてください。同期を減らして問題を小さくしてみてください。次に、可能な限り高いライブラリサポートを使用して同期を行います。そして、あなたが本当に持っている場合にのみ、並行性を自分で処理しようとします。各ワーカーが正しく作業を行っていることを知っていて、すべての考えから、並行性の問題が解決されたことがわかったら、興味深い負荷が発生します。ユニットテストフレームワークとその拡張機能はその仕事をすることができますが、あなたがもうユニットをテストしていないことを知っています。 (覚えておいてください、あなたはすでにその部分をカバーしていました)

並行性モデルが複雑になる場合は、 [〜#〜] spin [〜#〜] のようなツールをチェックしてください。

15
Rene

Javaの同時実行の実際 同時実行の問題のテストを作成する方法に関するいくつかの優れた情報があります。ただし、これらは真の単体テストではありません。並行性の問題について真の単体テストを作成することはほぼ不可能です。

基本的にはこれに要約されます。一連のテストスレッドを作成し、それらを開始します。各スレッドは

  • カウントダウンラッチを待つ
  • 問題の可変状態を変更するメソッドを繰り返し呼び出す
  • 2番目のラッチでカウントダウンして終了します

Junitスレッドはすべてのスレッドを作成して開始し、最初のラッチを1回カウントダウンしてすべてを解放し、2番目のラッチを待機してから、可変状態についていくつかのアサーションを作成します。

他のタイプのバグよりもさらに、並行性バグの失敗した単体テストを作成する方が簡単ですafterバグを見つけました。

21
Craig P. Motlin

あなたが解決しなければならない2つの基本的な問題があります。 1つ目はこれです:どのようにスレッドの衝突を生成しますか?次に、テストをどのように検証しますか?

最初のものは簡単です。大きなハンマーを使用してください。あるスレッドが別のスレッドを踏むケースを検出できるコードを記述し、そのコードを50回の連続したスレッドで50回ほど実行します。これは、カウントダウンラッチを使用して行うことができます。

public void testForThreadClash() {
  final CountDownLatch latch = new CountDownLatch(1);
  for (int i=0; i<50; ++i) {
    Runnable runner = new Runnable() {
      public void run() {
        try {
          latch.await();
          testMethod();
        } catch (InterruptedException ie) { }
      }
    }
    new Thread(runner, "TestThread"+i).start();
  }
  // all threads are waiting on the latch.
  latch.countDown(); // release the latch
  // all threads are now running concurrently.
}

TestMethod()は、同期されたブロックがないと、あるスレッドが別のスレッドのデータを踏む状況を生成する必要があります。このケースを検出して例外をスローできるはずです。 (上記のコードはこれを行いません。例外はテストスレッドの1つで生成される可能性が高く、メインスレッドでそれを検出するメカニズムを導入する必要がありますが、それは別の問題です。私はそれを残しました簡単にするために外に出しましたが、2番目のラッチを使用してこれを行うことができます。これは、例外でテストが途中でクリアされる可能性があります。)

2番目の質問は難しいですが、簡単な解決策があります。質問はこれです:あなたのハンマーが衝突を生成することをどうやって知っていますか?もちろん、コードには衝突を防ぐために同期されたブロックがあるため、質問に答えることはできません。でも君ならできる。 同期されたキーワードを削除するだけです。クラスをスレッドセーフにするのは同期されたキーワードなので、それらを削除してテストを再実行できます。テストが有効な場合、失敗します。

私が最初に上記のコードを書いたとき、それは無効であることがわかりました。私は衝突を見たことがありません。したがって、それは(まだ)有効なテストではありません。しかし今、私たちは2番目の質問に対する明確な答えを得る方法を知っています。間違った答えが返ってきましたが、テストをいじって探している失敗を生成することができます。これが私がしたことです:私はちょうど100回続けてテストを実行しました。

for (int j=0; j<100; ++j) {
  testForThreadClash();
}

今、私のテストは約20回目の反復で確実に失敗しました。これは私のテストが有効であることを確認します。これで、synchronizedキーワードを復元してテストを再実行できます。これにより、クラスがスレッドセーフであるかどうかがわかります。

10
MiguelMunoz

場合によっては、おそらくCountDownLatchまたはそのような同時実行メカニズムを使用して、互いに同期する複数のスレッドを使用することにより、スレッドセーフでない可能性のあるクラスへの呼び出しの特定の問題のあるインターリーブを強制できることがわかりました。ただし、これが機能しない場合もあります。たとえば、2つのスレッドが同時に同じメソッドにある場合に何が起こるかをテストしようとしている場合です。

ここに興味深い記事があります(ただし、ツールについてはよくわかりません): http://today.Java.net/pub/a/today/2003/08/06/multithreadedTests.html

1
Phil

スレッド化がテストするコードのポイントである場合、マルチスレッドコードの単体テストに問題はありません(並行データ構造を考えてください)。一般的なアプローチは、メインテストスレッドをブロックし、別々のスレッドでアサーションを実行し、メインスレッドで失敗したアサーションをキャプチャして再スローするか、メインテストスレッドのブロックを解除してテストを完了できるようにすることです。 ConcurrentUnit :を使用するのは非常に簡単です。

@Test
public void shouldDeliverMessage() throws Throwable {
  final Waiter waiter = new Waiter();

  messageBus.registerHandler(message -> {
    // Called on separate thread
    waiter.assertEquals(message, "foo");

    // Unblocks the waiter.await call
    waiter.resume();
  };

  messageBus.send("foo");

  // Wait for resume() to be called
  waiter.await(1000);
}

ここで重要なのは、任意のスレッドで失敗したアサーションがwaiterによってメインスレッドで再スローされ、テストが正常に合格または不合格になることです。

0
Jonathan