La maggior parte delle soluzioni lo faranno
- termina il test (metodo, non l'intera corsa) il momento
System.exit()
viene chiamato
- ignora un già installato
SecurityManager
- A volte è abbastanza specifico per un framework di test
- limitare l'uso massimo una volta per caso di test
Pertanto, la maggior parte delle soluzioni non è adatta a situazioni in cui:
- La verifica degli effetti collaterali deve essere eseguita dopo la chiamata a
System.exit()
- Un gestore della sicurezza esistente fa parte del test.
- Viene utilizzato un diverso framework di test.
- Desideri avere più verifiche in un singolo caso di test. Questo può essere rigorosamente sconsigliato, ma a volte può essere molto conveniente, specialmente in combinazione con
assertAll()
, per esempio.
Non ero contento delle restrizioni imposte dalle soluzioni esistenti presentate nelle altre risposte, e quindi ho escogitato qualcosa da solo.
La seguente classe fornisce un metodo assertExits(int expectedStatus, Executable executable)
che afferma di System.exit()
essere chiamato con un status
valore specificato e il test può continuare dopo di esso. Funziona allo stesso modo di JUnit 5assertThrows
. Rispetta anche un gestore della sicurezza esistente.
Rimane un problema: quando il codice in prova installa un nuovo gestore della sicurezza che sostituisce completamente il gestore della sicurezza impostato dal test. Tutte le altre SecurityManager
soluzioni basate su me conosciute soffrono dello stesso problema.
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;
}
}
}
Puoi usare la classe in questo modo:
@Test
void example() {
assertExits(0, () -> System.exit(0)); // succeeds
assertExits(1, () -> System.exit(1)); // succeeds
assertExits(2, () -> System.exit(1)); // fails
}
Il codice può essere facilmente trasferito su JUnit 4, TestNG o qualsiasi altro framework, se necessario. L'unico elemento specifico del framework sta fallendo il test. Questo può essere facilmente modificato in qualcosa di indipendente dal framework (diverso da Junit 4 Rule
Vi sono margini di miglioramento, ad esempio il sovraccarico assertExits()
di messaggi personalizzabili.