Junitを使用して同時実行テストを実行する方法
クラスがあるとしましょう
public class MessageBoard
{
public synchronized void postMessage(String message)
{
....
}
public void updateMessage(Long id, String message)
{
....
}
}
このpostMessageへの複数のアクセスを同時にテストしたいと思います。これについて何かアドバイスはありますか?この種の同時実行性テストをすべてのセッター関数(または作成/更新/削除操作を含むメソッド)に対して実行したいと思います。
残念ながら、ランタイムテストを使用して、コードがスレッドセーフであることを確実に証明できるとは思いません。あなたはそれに対して好きなだけ多くのスレッドを投げることができます、そして、それはスケジューリングに応じてパスするかもしれないししないかもしれません。
おそらく [〜#〜] pmd [〜#〜] などのいくつかの静的分析ツールを見て、同期の使用方法を判断し、使用上の問題を特定する必要があります。
MultithreadedTC を使用することをお勧めします Bill Pugh (および Nat Ayewah )。彼らの引用 概要 :
MultithreadedTCは、並行アプリケーションをテストするためのフレームワークです。複数のスレッドでのアクティビティのシーケンスを細かく制御するために使用されるメトロノームを備えています。
このフレームワークにより、個別のテストですべてのスレッドのインターリーブを確定的にテストできます
バグの不在ではなく、同時に発生するバグの存在を証明することができます。
ただし、複数の同時スレッドを生成して@Testアノテーション付きメソッドを呼び出す専用のテストランナーを作成できます。
.NETには、ユニットテストの同時実行性専用に設計された TypeMock Racer または Microsoft CHESS などのツールがあります。これらのツールは、デッドロックのようなマルチスレッドのバグを見つけるだけでなく、エラーを再現するスレッドインターリーブのセットも提供します。
Javaの世界でも同様のことがあると思います。
同時に実行すると、予期しない結果が生じる可能性があります。たとえば、ちょうど200個のテストを含むテストスイートが1つずつ実行されたときにテストパスは成功するが、同時実行に失敗することを発見しました。それは、スレッドセーフの問題ではなく、別のテストによるテストです。悪いことであり、私は問題を解決することができました。
JUnit ConcurrentJunitRunnerおよびConcurrentSuiteでのMycilaの作業 は非常に興味深いものです。この記事は、最新のGA=リリースに比べて少し古くなっているようです。私の例では、更新された使用法を示します。
次のようなテストクラスにアノテーションを付けると、並行性レベル6でテストメソッドが同時に実行されます。
import com.mycila.junit.concurrent.ConcurrentJunitRunner;
import com.mycila.junit.concurrent.Concurrency;
@RunWith(ConcurrentJunitRunner.class)
@Concurrency(6)
public final class ATest {
...
すべてのテストクラスを同時に実行することもできます。
import com.mycila.junit.concurrent.ConcurrentSuiteRunner;
@RunWith(ConcurrentSuiteRunner.class)
@Suite.SuiteClasses({ATest.class, ATest2.class, ATest3.class})
public class MySuite {
}
Maven依存関係 は次のとおりです。
<dependency>
<groupId>com.mycila</groupId>
<artifactId>mycila-junit</artifactId>
<version>1.4.ga</version>
</dependency>
現在、メソッドを複数回、このパッケージと同時に実行する方法を調査しています。誰かが私に知らせた例があれば、私の自作のソリューションの下ですでに可能であるかもしれません。
@Test
public final void runConcurrentMethod() throws InterruptedException {
ExecutorService exec = Executors.newFixedThreadPool(16);
for (int i = 0; i < 10000; i++) {
exec.execute(new Runnable() {
@Override
public void run() {
concurrentMethod();
}
});
}
exec.shutdown();
exec.awaitTermination(50, TimeUnit.SECONDS);
}
private void concurrentMethod() {
//do and assert something
}
他の人が指摘しているように、同時実行のバグが現れるかどうかは確実ではありませんが、たとえば16の同時実行で1万または10万回の実行では、統計はあなたの側にあります。
JUnitに同梱されているActiveTestSuiteを見てください。複数のJUnitテストを同時に起動できます。
public static Test suite()
{
TestSuite suite = new ActiveTestSuite();
suite.addTestSuite(PostMessageTest.class);
suite.addTestSuite(PostMessageTest.class);
suite.addTestSuite(PostMessageTest.class);
suite.addTestSuite(PostMessageTest.class);
suite.addTestSuite(PostMessageTest.class);
return suite;
}
上記は、同じJUnitテストクラスを並行して5回実行します。並列テストのバリエーションが必要な場合は、別のクラスを作成してください。
あなたの例では、postMessage()メソッドはsynchronizedであるため、実際には単一のVM内からの同時実行性の影響は確認できませんが、同期バージョンのパフォーマンス。
テストプログラムの複数のコピーを異なるVMで同時に実行する必要があります。使用できます
テストフレームワークで実行できない場合は、一部のVMを自分で起動できます。プロセスビルダーのものはパスなどに苦労しますが、一般的なスケッチは次のとおりです。
Process running[] = new Process[5];
for (int i = 0; i < 5; i++) {
ProcessBuilder b = new ProcessBuilder("Java -cp " + getCP() + " MyTestRunner");
running[i] = b.start();
}
for(int i = 0; i < 5; i++) {
running[i].waitFor();
}
私は通常、他の人が投稿したように、単純なスレッドテストに対してこのようなことをします。テストは正確さの証明ではありませんが、実際にはばかげたバグを排除します。これは、さまざまな異なる条件下で長期間テストするのに役立ちます。同時実行のバグがテストに現れるまでに時間がかかることがあります。
public void testMesageBoard() {
final MessageBoard b = new MessageBoard();
int n = 5;
Thread T[] = new Thread[n];
for (int i = 0; i < n; i++) {
T[i] = new Thread(new Runnable() {
public void run() {
for (int j = 0; j < maxIterations; j++) {
Thread.sleep( random.nextInt(50) );
b.postMessage(generateMessage(j));
verifyContent(j); // put some assertions here
}
}
});
PerfTimer.start();
for (Thread t : T) {
t.start();
}
for (Thread t : T) {
t.join();
}
PerfTimer.stop();
log("took: " + PerfTimer.elapsed());
}
}**strong text**
同時実行バグのテストは不可能です。入力/出力のペアを検証するだけでなく、テスト中に発生する場合と発生しない場合がある状況で状態を検証する必要があります。残念ながら、JUnitにはこれを行う機能がありません。
Tempus-fugitライブラリを使用して、テストメソッドを並行して複数回実行し、負荷テストタイプの環境をシミュレートできます。以前のコメントは、投稿メソッドが同期化されて保護されていることを指摘していますが、関連するメンバーまたはメソッドが含まれている可能性があり、それら自体は保護されていないため、可能ロード/ソークタイプテストがこれらをキャッチできることループホールをキャッチする可能性を高めるために、かなり粗い/エンドツーエンドのようなテストをセットアップすることをお勧めします。
documentation のJUnit統合セクションを参照してください。
ところで、私は言ったプロジェクトの開発者:)
HavaRunner を試すこともできます。デフォルトで並行してテストを実行します。
ここでの最良のアプローチは、システムテストを使用して、コードが倒れるかどうかを確認するリクエストでコードを爆破することです。次に、単体テストを使用して論理的な正当性を確認します。私がこれに取り組む方法は、非同期呼び出しのプロキシを作成し、テスト下で同期させることです。
ただし、完全インストールおよび完全環境以外のレベルでこれを実行したい場合は、別のスレッドでオブジェクトを作成することにより、junitでこれを行うことができます。次に、オブジェクトへの要求を起動してメインスレッドをブロックする多数のスレッドを作成します。それが完了するまで。このアプローチでは、正しく行わないとテストが断続的に失敗する可能性があります。