ほとんどのソリューションは
- テストが終了する(メソッド全体、実行全体ではない)
System.exit()
と呼ばれる瞬間
- すでにインストールされているものを無視する
SecurityManager
- テストフレームワークに固有の場合
- テストケースごとに最大1回の使用に制限する
したがって、ほとんどのソリューションは次のような状況には適していません。
- 副作用の検証は、
System.exit()
- 既存のセキュリティマネージャはテストの一部です。
- 別のテストフレームワークが使用されます。
- 1つのテストケースで複数の検証を行いたい。これは厳密にはお勧めできませんが、
assertAll()
たとえばとの組み合わせでは特に便利な場合があります。
他の回答で提示された既存のソリューションによって課せられた制限に満足できなかったため、自分で何かを思い付きました。
次のクラスは、指定された値で呼び出されるassertExits(int expectedStatus, Executable executable)
ことを表明するメソッドを提供し、テストはその後も続行できます。JUnit 5と同じように動作します。また、既存のセキュリティマネージャを尊重します。System.exit()
status
assertThrows
残っている問題が1つあります。テスト対象のコードが新しいセキュリティマネージャーをインストールすると、テストによって設定されたセキュリティマネージャーが完全に置き換えられます。SecurityManager
私が知っている他のすべてのベースのソリューションは同じ問題を抱えています。
import java.security.Permission;
import static java.lang.System.getSecurityManager;
import static java.lang.System.setSecurityManager;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;
public enum ExitAssertions {
;
public static <E extends Throwable> void assertExits(final int expectedStatus, final ThrowingExecutable<E> executable) throws E {
final SecurityManager originalSecurityManager = getSecurityManager();
setSecurityManager(new SecurityManager() {
@Override
public void checkPermission(final Permission perm) {
if (originalSecurityManager != null)
originalSecurityManager.checkPermission(perm);
}
@Override
public void checkPermission(final Permission perm, final Object context) {
if (originalSecurityManager != null)
originalSecurityManager.checkPermission(perm, context);
}
@Override
public void checkExit(final int status) {
super.checkExit(status);
throw new ExitException(status);
}
});
try {
executable.run();
fail("Expected System.exit(" + expectedStatus + ") to be called, but it wasn't called.");
} catch (final ExitException e) {
assertEquals(expectedStatus, e.status, "Wrong System.exit() status.");
} finally {
setSecurityManager(originalSecurityManager);
}
}
public interface ThrowingExecutable<E extends Throwable> {
void run() throws E;
}
private static class ExitException extends SecurityException {
final int status;
private ExitException(final int status) {
this.status = status;
}
}
}
次のようなクラスを使用できます。
@Test
void example() {
assertExits(0, () -> System.exit(0)); // succeeds
assertExits(1, () -> System.exit(1)); // succeeds
assertExits(2, () -> System.exit(1)); // fails
}
コードは、必要に応じてJUnit 4、TestNG、またはその他のフレームワークに簡単に移植できます。フレームワーク固有の要素は、テストに失敗したことだけです。これは、フレームワークに依存しないもの(Junit 4以外)に簡単に変更できます。 Rule
たとえば、assertExits()
カスタマイズ可能なメッセージによるオーバーロードなど、改善の余地があります。