Большинство решений будет
- прекратить тест (метод, а не весь прогон) момент
System.exit()
вызова
- игнорировать уже установленный
SecurityManager
- Иногда быть достаточно специфичным для тестовой среды
- ограничить использование максимум один раз для каждого теста
Таким образом, большинство решений не подходят для ситуаций, когда:
- Проверка побочных эффектов должна быть выполнена после звонка
System.exit()
- Существующий менеджер безопасности является частью тестирования.
- Используется другая структура тестирования.
- Вы хотите иметь несколько проверок в одном тестовом случае. Это может быть строго не рекомендовано, но иногда может быть очень удобно, особенно в сочетании
assertAll()
, например, с.
Я не был доволен ограничениями, наложенными существующими решениями, представленными в других ответах, и поэтому придумал что-то самостоятельно.
Следующий класс предоставляет метод, assertExits(int expectedStatus, Executable executable)
который утверждает, что System.exit()
вызывается с указанным status
значением, и тест может продолжаться после него. Он работает так же, как JUnit 5 assertThrows
. Это также уважает существующего менеджера по безопасности.
Осталась одна проблема: когда тестируемый код устанавливает новый менеджер безопасности, который полностью заменяет менеджер безопасности, установленный тестом. Все остальные 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()
настраиваемыми сообщениями.