La plupart des solutions
- terminer le test (méthode, pas la totalité du cycle) le moment
System.exit()
est appelé
- ignorer un déjà installé
SecurityManager
- Parfois, être assez spécifique à un framework de test
- restreindre pour être utilisé au maximum une fois par scénario de test
Ainsi, la plupart des solutions ne conviennent pas aux situations où:
- La vérification des effets secondaires doit être effectuée après l'appel à
System.exit()
- Un gestionnaire de sécurité existant fait partie des tests.
- Un cadre de test différent est utilisé.
- Vous souhaitez avoir plusieurs vérifications dans un même scénario de test. Cela peut être strictement déconseillé, mais peut parfois être très pratique, en particulier en combinaison avec
assertAll()
, par exemple.
Je n'étais pas satisfait des restrictions imposées par les solutions existantes présentées dans les autres réponses, et j'ai donc trouvé quelque chose par moi-même.
La classe suivante fournit une méthode assertExits(int expectedStatus, Executable executable)
qui affirme qu'elle System.exit()
est appelée avec un spécifiéstatus
valeur et le test peut continuer après. Cela fonctionne de la même manière que JUnit 5assertThrows
. Il respecte également un gestionnaire de sécurité existant.
Il reste un problème: lorsque le code sous test installe un nouveau gestionnaire de sécurité qui remplace complètement le gestionnaire de sécurité défini par le test. Toutes les autres SecurityManager
solutions à ma connaissance connaissent le même problème.
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;
}
}
}
Vous pouvez utiliser la classe comme ceci:
@Test
void example() {
assertExits(0, () -> System.exit(0)); // succeeds
assertExits(1, () -> System.exit(1)); // succeeds
assertExits(2, () -> System.exit(1)); // fails
}
Le code peut facilement être porté sur JUnit 4, TestNG ou tout autre framework, si nécessaire. Le seul élément spécifique au framework échoue au test. Cela peut facilement être changé en quelque chose d'indépendant du framework (autre qu'un Junit 4 Rule
Il y a place à amélioration, par exemple, la surcharge assertExits()
de messages personnalisables.