46

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

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

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

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

4

5 に答える 5

24

Java Concurrency in Practiceには、並行性の問題に対するテストの作成方法に関する優れた情報が含まれています。ただし、それらは真の単体テストではありません。並行性の問題に対する真の単体テストを作成することはほぼ不可能です。

基本的にはこれに尽きます。一連のテスト スレッドを作成して開始します。各スレッドは

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

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

他の種類のバグよりも、バグを見つけたで、同時実行のバグに対して失敗する単体テストを作成する方が簡単です。

于 2009-01-08T17:45:14.017 に答える
16

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

同時実行モデルが複雑になる場合は、 SPINのようなそれに適したツールをチェックしてください。

于 2009-01-08T15:52:38.347 に答える
15

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

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

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

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

于 2013-02-08T21:14:21.877 に答える
1

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

これは興味深い記事です (ツールについてはあまり知りません): http://today.java.net/pub/a/today/2003/08/06/multithreadedTests.html

于 2009-01-08T15:59:57.910 に答える
0

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

于 2016-04-20T18:20:49.570 に答える