S-JIS[2022-08-19] 変更履歴
JUnit5のTimeoutアノテーションについて。
|
JUnit5.5(正式版としてはJUnit5.7)でTimeoutアノテーションが追加された。
テストの実行が、Timeoutで指定した時間を経過するとタイムアウトする。
タイムアウトすると、そのテストは失敗扱いとなる。
import java.util.concurrent.TimeUnit;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Timeout;
class TimeoutExample {
@Test
@Timeout(10) // 10秒でタイムアウト
void sleep() throws InterruptedException {
TimeUnit.SECONDS.sleep(30);
}
}
このタイムアウトは、スレッドに割り込む(interruptする)ことで実現しているようだ。
このため、タイムアウト時間が経過したら即座にテストが停止するわけではない。
(テスト対象メソッドが割り込みを受けてから終了するまでの処理内容に依存する)
テスト対象がスレッドの割り込み(interrupt)を正しく処理していないと、タイムアウト(停止)しない。
| 例 | |
|---|---|
| 秒を指定 | @Timeout(10) // 10秒でタイムアウト |
| 単位を指定 | @Timeout(value = 1, unit = TimeUnit.MINUTES) // 1分でタイムアウト |
| クラスに指定 | @Timeout(10) // 全テストメソッドにタイムアウトを適用 |
Timeoutアノテーションによるタイムアウトは、スレッドに割り込む(interruptする)ことで実現しているようだ。
Thread.sleep()(TimeUnit.XXX.sleep())を使ってスリープしている場合、テストのタイムアウト時にsleepメソッドからInterruptedExceptionがスローされる。
@Test
@Timeout(10) // 10秒でタイムアウト
void sleep() throws InterruptedException {
TimeUnit.SECONDS.sleep(30); // 10秒経つとThreadがinterruptされてInterruptedExceptionがスローされる
}
以下のような無限ループだと、タイムアウト時間を経過してもテストは終了しない(タイムアウトしない)。
@Test
@Timeout(10) // 10秒でタイムアウト
void loop() {
for (;;) { // 無限ループ
}
}
@Test
@Timeout(10) // 10秒でタイムアウト
void sleepLoop() {
for (;;) { // 無限ループ
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException ignore) {
// InterruptedExceptionを握りつぶす
}
}
}
※このような事があるので、Thread.sleep()のInterruptedExceptionは握りつぶしてはいけない。
Thread.interrupted()を呼び出せば、割り込みが発生しているかどうかを確認できる。
@Test
@Timeout(10) // 10秒でタイムアウト
void loop() {
for (;;) {
if (Thread.interrupted()) {
System.out.println("loop: interrupted");
break; // ループを終了
}
}
}
ちなみに、割り込みによってThread.interrupted()がtrueになるのは1回だけのようだ。
trueになってから次にThread.interrupted()を呼び出したら、falseが返る。
java.net.SocketやServerSocketは、スレッドの割り込みに対応していない。
つまり、これらを使って通信しているメソッドをテストする場合、Timeoutアノテーションは機能しない。
SocketやServerSocketを使う場合は、そもそもソケットのタイムアウト時間を指定しておくべき。
try (var socket = new Socket("localhost", 123)) {
socket.setSoTimeout((int) TimeUnit.MINUTES.toMillis(1));
〜
}
try (var server = new ServerSocket(123)) {
server.setSoTimeout((int) TimeUnit.MINUTES.toMillis(30)); // acceptのタイムアウト時間
try (var socket = server.accept()) {
socket.setSoTimeout((int) TimeUnit.MINUTES.toMillis(1));
〜
}
}
タイムアウトしたときにテストメソッドから例外を投げると、タイムアウトの例外にaddSuppressedされる。
@Test
@Timeout(10)
void testSleep() throws InterruptedException {
TimeUnit.SECONDS.sleep(30); //InterruptedException
}
|
@Test
@Timeout(10)
void testSleep() {
try {
TimeUnit.SECONDS.sleep(30);
} catch (InterruptedException ignore) {
return;
}
}
|
java.util.concurrent.TimeoutException: testSleep() timed out after 10 second |
java.util.concurrent.TimeoutException: testSleep() timed out after 10 second |