Die meisten Lösungen werden
- Beenden Sie den Test (Methode, nicht den gesamten Lauf), sobald der Moment
System.exit()
aufgerufen wird
- ignoriere eine bereits installierte
SecurityManager
- Seien Sie manchmal sehr spezifisch für ein Test-Framework
- Beschränken Sie die Verwendung auf maximal einmal pro Testfall
Daher sind die meisten Lösungen nicht für Situationen geeignet, in denen:
- Die Überprüfung der Nebenwirkungen ist nach dem Anruf bei durchzuführen
System.exit()
- Ein vorhandener Sicherheitsmanager ist Teil des Tests.
- Ein anderes Testframework wird verwendet.
- Sie möchten mehrere Überprüfungen in einem einzigen Testfall durchführen. Dies kann strengstens nicht empfohlen werden, kann aber manchmal sehr praktisch sein, insbesondere in Kombination mit
assertAll()
beispielsweise.
Ich war mit den Einschränkungen, die durch die vorhandenen Lösungen in den anderen Antworten auferlegt wurden, nicht zufrieden und kam daher auf meine eigene Idee.
Die folgende Klasse stellt eine Methode bereit, assertExits(int expectedStatus, Executable executable)
die bestätigt, dass System.exit()
sie mit einem angegebenen status
Wert aufgerufen wird, und der Test kann danach fortgesetzt werden. Es funktioniert genauso wie JUnit 5assertThrows
. Es respektiert auch einen vorhandenen Sicherheitsmanager.
Es gibt noch ein Problem: Wenn der zu testende Code einen neuen Sicherheitsmanager installiert, der den durch den Test festgelegten Sicherheitsmanager vollständig ersetzt. Alle anderen SecurityManager
mir bekannten Lösungen haben das gleiche Problem.
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;
}
}
}
Sie können die Klasse folgendermaßen verwenden:
@Test
void example() {
assertExits(0, () -> System.exit(0)); // succeeds
assertExits(1, () -> System.exit(1)); // succeeds
assertExits(2, () -> System.exit(1)); // fails
}
Der Code kann bei Bedarf problemlos auf JUnit 4, TestNG oder ein anderes Framework portiert werden. Das einzige Framework-spezifische Element besteht darin, den Test nicht zu bestehen. Dies kann leicht in etwas Framework-unabhängiges geändert werden (außer einem Junit 4) Rule
Es gibt Raum für Verbesserungen, zum Beispiel das Überladen assertExits()
mit anpassbaren Nachrichten.