Większość rozwiązań będzie
- zakończ test (metoda, a nie cały przebieg),
System.exit()
wywoływany jest moment
- zignoruj już zainstalowany
SecurityManager
- Czasami są dość specyficzne dla środowiska testowego
- ograniczyć do użycia maksymalnie raz na przypadek testowy
Dlatego większość rozwiązań nie nadaje się do sytuacji, w których:
- Weryfikację skutków ubocznych należy przeprowadzić po wezwaniu do
System.exit()
- Istniejący menedżer bezpieczeństwa jest częścią testów.
- Używana jest inna platforma testowa.
- Chcesz mieć wiele weryfikacji w jednym przypadku testowym. Może to nie być absolutnie zalecane, ale czasami może być bardzo wygodne, szczególnie w połączeniu z
assertAll()
, na przykład.
Nie byłem zadowolony z ograniczeń narzuconych przez istniejące rozwiązania przedstawione w innych odpowiedziach, dlatego sam coś wymyśliłem.
Następująca klasa udostępnia metodę, assertExits(int expectedStatus, Executable executable)
która zapewnia, że System.exit()
jest wywoływana z określoną status
wartością, a po niej można kontynuować test. Działa tak samo jak JUnit 5assertThrows
. Szanuje również istniejącego menedżera bezpieczeństwa.
Pozostaje jeszcze jeden problem: gdy testowany kod instaluje nowego menedżera bezpieczeństwa, który całkowicie zastępuje menedżera bezpieczeństwa ustawionego przez test. Wszystkie inne SecurityManager
znane mi rozwiązania oparte są na tym samym problemie.
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;
}
}
}
Możesz użyć tej klasy w następujący sposób:
@Test
void example() {
assertExits(0, () -> System.exit(0)); // succeeds
assertExits(1, () -> System.exit(1)); // succeeds
assertExits(2, () -> System.exit(1)); // fails
}
W razie potrzeby kod można łatwo przenieść do JUnit 4, TestNG lub dowolnego innego środowiska. Jedynym elementem specyficznym dla frameworka jest test zakończony niepowodzeniem. Można to łatwo zmienić na coś niezależnego od frameworka (innego niż Junit 4 Rule
Jest miejsce na ulepszenia, na przykład przeciążenie assertExits()
dostosowywanymi komunikatami.