11

エグゼクティブ サマリー:スレッドでアサーション エラーがスローされても、単体テストは終了しません。あるスレッドが別のスレッドをクラッシュさせてはならないため、これは理にかなっています。問題は、1) 最初のヘルパー スレッドがクラッシュしたときにテスト全体を失敗させるか、2) ループしてすべてのスレッドが完了した後に各スレッドの状態を判断する方法です (以下のコードを参照)。後者を行う 1 つの方法は、スレッドごとのステータス変数、たとえば「boolean[] statuses」を使用し、「statuses[i] == false」をスレッドが失敗したことを意味するようにすることです (これは、より多くの情報を取得するために拡張できます)。ただし、それは私が望んでいることではありません。アサーション エラーがスローされたときに、他の単体テストと同じように失敗するようにしたいのです。これは可能ですか?それは望ましいですか?

うんざりしたので、単体テストで多数のスレッドを生成し、それらにサービス メソッドを呼び出させることにしました。コードはおおよそ次のようになります。

Thread[] threads = new Thread[MAX_THREADS];
for( int i = 0; i < threads.length; i++ ) {
    threads[i] = new Thread( new Runnable() {
        private final int ID = threadIdSequenceNumber++;
        public void run() {
            try {
                resultRefs[ID] = runTest( Integer.toString( ID ) ); // returns an object
            }
            catch( Throwable t ) { 
                // this code is EVIL - it catches even
                // Errors - don't copy it - more on this below
                final String message = "error testing thread with id => "
                            + ID;
                logger.debug( message, t );
                throw new IllegalStateException( message, t ); 
                // need to wrap throwable in a 
                // run time exception so it will compile
            }
        }
    } );
}

この後、スレッドの配列をループして、それぞれを開始します。その後、すべてが完了するのを待ちます。最後に、結果参照に対していくつかのチェックを実行します。

for( Thread thread : threads )
    thread.start();

logger.debug( "waiting for threads to finish ..." );
boolean done = false;
while( !done ) {
    done = true;
    for( Thread thread : threads )
        if( thread.isAlive() )
            done = false;
}

for( int i = 0; i < resultRefs.length; i++ ) {
    assertTrue( "you've got the world messed, dawg!",
            myCondition(resultRefs[i]) );

これが問題です。厄介な try-catch-throwable ブロックに気付きましたか? 何が起こっているのかを確認できるように、一時的なハックとして追加しました。runTest( String ) では、 assertNotNull( null ) など、いくつかのアサーションが作成されますが、別のスレッドにあるため、単体テストが失敗することはありません!!!!

私の推測では、どうにかしてスレッド配列を繰り返し処理し、それぞれのステータスをチェックし、スレッドが不適切な方法で終了した場合は手動でアサーション エラーを発生させる必要があると思います。この情報 (デッド スレッドのスタック トレース) を提供するメソッドの名前は?

4

7 に答える 7

8

並行性は、単体テストが非常に難しいものの 1 つです。各スレッド内のコードがテストすべきことを実行していることをテストしようとしているだけの場合は、このコードをコンテキストから分離してテストする必要があります。この例で、スレッドが協力して結果に到達する場合、スレッドを使用せずにそのコラボレーションをテストできる可能性があります。これは、すべての共同作業を順番に実行することによって行われます。競合状態などをテストしたい場合、単体テストは最善の方法ではありません。失敗することもあれば、失敗しないこともあるテストが得られます。要約すると、あなたの問題は、高すぎるレベルで単体テストを行っていることだと思います。お役に立てれば

于 2008-11-24T16:44:09.403 に答える
5

Google テスト ブログには、このテーマに関する優れた記事があり、読む価値があります: http://googletesting.blogspot.com/2008/08/tott-sleeping-synchronization.html

これは Python で書かれていますが、原則はそのまま Java に移植できると思います。

于 2008-11-24T16:49:13.200 に答える
2

マルチスレッド環境での単体テストは大変です...そのため、いくつかの調整が必要です。単体テストは繰り返し可能でなければなりません..決定論的です。その結果、複数のスレッドを持つものはすべてこの基準を満たしていません。複数のスレッドを使用したテストも遅くなる傾向があります。

  • 単一のスレッドでのテストでうまくいくかどうかを確認しようとします..テスト中のロジックは本当に複数のスレッドを必要としますか.
  • それが機能しない場合は、すべてのスレッドの実行が終了したときに、テストの最後に期待値に対してチェックできるメンバー変数アプローチを使用してください。

ねえ、このような別の質問があるようです。tdd yahoogroup Unit testing a multithreaded application?でのより長いディスカッションへのリンクについては、私の投稿を確認してください。

于 2008-11-24T16:49:48.987 に答える
1

実行可能なラッパーは、例外オブジェクトをテスト クラスに戻してから、それらをコレクションに格納する必要があります。すべてのテストが終了したら、コレクションをテストできます。空でない場合は、各例外を反復処理し、.printStackTrace() を失敗させます。

于 2008-11-24T18:25:01.837 に答える
1

いくつかのフラグ (スレッドが定期的にチェックする) を設定するUncaughtExceptionHandlerを実装し、各 Thread に設定します。

于 2008-11-24T18:32:47.407 に答える
0

特別な同期オブジェクトを使用することで、単体テストを失敗させることができます。次の記事をご覧ください: スプリンクラー - 高度な同期オブジェクト

ここでは要点を説明しようと思います。内部スレッドの失敗をメインスレッドに外部化できるようにしたいと考えています。これは、あなたの場合はテストです。したがって、内部スレッドとテストの両方が相互に同期するために使用する共有オブジェクト/ロックを使用する必要があります。次のテストを参照してください。Sprinkler という名前の共有オブジェクトを呼び出して、スローされた例外をシミュレートするスレッドを作成します。メイン スレッド (テスト) は Sprinkler.getInstance().await(CONTEXT, 10000) でブロックされ、リリースが呼び出されるまでに解放され、スローされた例外をキャッチします。catch ブロックでは、テストに失敗する assert を記述できます。

 @Test
    public void testAwait_InnerThreadExternalizeException() {

        final int CONTEXT = 1;
        final String EXCEPTION_MESSAGE = "test inner thread exception message";

        // release will occur sometime in the future - simulate exception in the releaser thread
        ExecutorServiceFactory.getCachedThreadPoolExecutor().submit(new Callable<void>() {

            @Override
            public Void call() throws Exception {

                Sprinkler.getInstance().release(CONTEXT, new RuntimeException(EXCEPTION_MESSAGE));

                return null;
            }

        });

        Throwable thrown = null;
        try {
            Sprinkler.getInstance().await(CONTEXT, 10000);
        } catch (Throwable t) {
            // if the releaser thread delivers exception it will be externelized to this thread
            thrown = t;
        }
        Assert.assertTrue(thrown instanceof SprinklerException);
        Assert.assertEquals(EXCEPTION_MESSAGE, thrown.getCause().getMessage());
    }
于 2013-12-02T10:47:03.100 に答える
0

Junit の並行スレッド テストのもう 1 つの一般的なオプションは、カスタム JunitRunner と単純な注釈を使用する Matthieu Carbou の方法です。

完全なドキュメントを見る

于 2012-03-24T13:23:09.663 に答える