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