У меня есть несколько методов, которые должны вызывать System.exit() на определенных входах. К сожалению, тестирование этих случаев приводит к завершению работы JUnit! Помещение вызовов методов в новый поток, похоже, не помогает, поскольку System.exit() завершает работу JVM, а не только текущий поток. Есть ли какие-нибудь общие шаблоны для решения этой проблемы? Например, можно ли заменить System.exit() заглушкой?
[EDIT] Рассматриваемый класс на самом деле является инструментом командной строки, который я пытаюсь протестировать внутри JUnit. Может быть, JUnit просто не подходит для работы? Приветствуются предложения по дополнительным инструментам регрессионного тестирования (желательно то, что хорошо интегрируется с JUnit и EclEmma).
Если вы вызываете функцию, выходящую из приложения. Например, если пользователь пытается выполнить задачу, которую ему не разрешено выполнять более x раз подряд, вы вынуждаете его выйти из приложения.
Я все еще думаю, что в этом случае должен быть более удобный способ вернуться из приложения, чем System.exit ().
Если вы тестируете main (), имеет смысл вызвать System.exit (). У нас есть требование, чтобы при ошибке пакетный процесс завершался с 1, а при успешном завершении - с 0.
Я не согласен со всеми, кто говорит, что System.exit() - это плохо - ваша программа должна быстро выходить из строя. Создание исключения просто продлевает работу приложения в недопустимом состоянии в ситуациях, когда разработчик хочет выйти, и выдаст ложные ошибки.
@ThomasOwens Мне любопытно, почему вы думаете, что System.exit плохой или не требуется. Подскажите пожалуйста, как вернуть пакет с кодом выхода 20 или 30 из java-приложения.




Беглый взгляд на api показывает, что System.exit может генерировать исключение esp. если менеджер безопасности запрещает выключение vm. Может решением было бы установить такой менеджер.
Действительно, Derkeiler.com предлагает:
System.exit()?Instead of terminating with System.exit(whateverValue), why not throw an unchecked exception? In normal use it will drift all the way out to the JVM's last-ditch catcher and shut your script down (unless you decide to catch it somewhere along the way, which might be useful someday).
In the JUnit scenario it will be caught by the JUnit framework, which will report that such-and-such test failed and move smoothly along to the next.
System.exit() из JVM:Try modifying the TestCase to run with a security manager that prevents calling System.exit, then catch the SecurityException.
public class NoExitTestCase extends TestCase
{
protected static class ExitException extends SecurityException
{
public final int status;
public ExitException(int status)
{
super("There is no escape!");
this.status = status;
}
}
private static class NoExitSecurityManager extends SecurityManager
{
@Override
public void checkPermission(Permission perm)
{
// allow anything.
}
@Override
public void checkPermission(Permission perm, Object context)
{
// allow anything.
}
@Override
public void checkExit(int status)
{
super.checkExit(status);
throw new ExitException(status);
}
}
@Override
protected void setUp() throws Exception
{
super.setUp();
System.setSecurityManager(new NoExitSecurityManager());
}
@Override
protected void tearDown() throws Exception
{
System.setSecurityManager(null); // or save and restore original
super.tearDown();
}
public void testNoExit() throws Exception
{
System.out.println("Printing works");
}
public void testExit() throws Exception
{
try
{
System.exit(42);
} catch (ExitException e)
{
assertEquals("Exit status", 42, e.status);
}
}
}
Обновление за декабрь 2012 г .:
Воля предлагает в комментариях с использованием Системные правила, набора правил JUnit (4.9+) для тестирования кода, который использует java.lang.System.
Первоначально об этом упоминал Стефан Биркнер в его ответ в декабре 2011 года.
System.exit(…)
Use the
ExpectedSystemExitrule to verify thatSystem.exit(…)is called.
You could verify the exit status, too.
Например:
public void MyTest {
@Rule
public final ExpectedSystemExit exit = ExpectedSystemExit.none();
@Test
public void noSystemExit() {
//passes
}
@Test
public void systemExitWithArbitraryStatusCode() {
exit.expectSystemExit();
System.exit(0);
}
@Test
public void systemExitWithSelectedStatusCode0() {
exit.expectSystemExitWithStatus(0);
System.exit(0);
}
}
Не понравился первый ответ, но второй довольно крутой - я не связывался с менеджерами по безопасности и предполагал, что они более сложные, чем это. Однако как вы тестируете менеджер безопасности / механизм тестирования.
Убедитесь, что удаление выполняется правильно, иначе ваш тест не удастся выполнить в средстве выполнения, таком как Eclipse, потому что приложение JUnit не может выйти! :)
Мне не нравится решение, использующее диспетчер безопасности. Мне кажется, что это просто взлом, чтобы проверить это.
Мне нравится это решение, но оно не работает. Я использую порт getopt Аарона М. Ренна, который выполняет System.getProperty("gnu.posixly_correct", null) (Getopt.java, строка 615). Это вызывает исключение безопасности при установке NoExitSecurityManager. Я также попробовал более сложный NoExitSecurityManager из библиотеки SystemRules, упомянутой в другом ответе, но он также вызывает исключение.
@bbuser то из urbanophile.com/arenn/hacking/getopt? Интересно. Это было бы лучше всего в отдельном вопросе (со ссылкой на этот вопрос), чтобы представить то, что вы пробовали, и исключение безопасности, которое оно генерирует.
@VonC Не согласен с первым ответом. Некоторые программы Java написаны для выполнения сценариями, ожидающими 0 = успех, 1 = сбой. AFAIK, спецификация Java не заставляет JVM возвращать ненулевой код ошибки, если возникает неперехваченное исключение. Хотя второй ответ выглядит неплохо.
@DuncanJones перечитывает docs.oracle.com/javase/specs/jvms/se7/html/…, вы правы (в отношении отсутствия фиксированного статуса выхода при завершении JVM из-за непроверенного исключения)
Если вы используете junit 4.7 или выше, позвольте библиотеке заниматься перехватом ваших вызовов System.exit. Системные правила - stefanbirkner.github.com/system-rules
@ Будет интересное предложение. Я включил его в этот (старый) ответ для большей наглядности.
«Вместо того, чтобы завершить работу с помощью System.exit (somethingValue), почему бы не создать непроверенное исключение?» - потому что я использую структуру обработки аргументов командной строки, которая вызывает System.exit всякий раз, когда предоставляется недопустимый аргумент командной строки.
Ничего против @Will, но этот ответ на самом деле предшествует его комментарию, поэтому я думаю, что его автор должен получить кредит: stackoverflow.com/a/8658497/1450294
@MichaelScheper, верно, я добавил в ответ правильную атрибуцию.
Проголосовал против: не нравится первый пункт. Я согласен, что System.exit не является хорошей практикой, однако это не ответ на заданный вопрос. не нравится второй пункт. Путаница с менеджером безопасности Java несовместима с модульным тестом. Мне нравится третий пункт, но это просто копия и вставка из ответа Стефана Биркнера ниже. Его ответ должен быть принятым, а не этим.
Мне понравилось решение поймать System.exit (). Мне нужно контролировать жизнь веб-движка в моей тестовой среде, а политика безопасности компании требует, чтобы он явно вызывал exit (), поэтому я необходимость, чтобы поймать это. Я добавил дополнительную проверку в strackTrace, если движок был вызывающим кодом exit (), и это просто хорошо для моих нужд. Проголосовал! Но для целей тестирования Системные правила - гораздо более элегантный подход.
Привет! Мои тесты не завершатся неудачно, даже если в коде после exit.expectSystemExit () нет system.exit (); линия. Есть идеи, почему это произошло?
@hyperlink 12 лет спустя, я не уверен: вы, возможно, захотите задать отдельный вопрос, касающийся ваших текущих Java и ОС 2020 года, которые, безусловно, отличаются от моих в то время, когда я писал это.
Вызов System.exit () - плохая практика, если только это не делается внутри main (). Эти методы должны генерировать исключение, которое, в конечном итоге, перехватывается вашим main (), который затем вызывает System.exit с соответствующим кодом.
Однако это не отвечает на вопрос. Что, если тестируемая функция ЯВЛЯЕТСЯ главным методом? Таким образом, вызов System.exit () может быть допустимым и приемлемым с точки зрения дизайна. Как написать для него тестовый пример?
Вам не нужно тестировать основной метод, так как основной метод должен просто принимать любые аргументы, передавать их методу синтаксического анализатора, а затем запускать приложение. В основном тестируемом методе не должно быть логики.
@Elie: На вопросы такого типа есть два правильных ответа. Один отвечает на заданный вопрос, а другой спрашивает, почему вопрос был основан. Оба типа ответов дают лучшее понимание, особенно оба вместе.
Один трюк, который мы использовали в нашей базе кода, заключался в том, чтобы вызов System.exit () был инкапсулирован в Runnable impl, который рассматриваемый метод использовал по умолчанию. Для модульного тестирования мы установили другой макет Runnable. Что-то вроде этого:
private static final Runnable DEFAULT_ACTION = new Runnable(){
public void run(){
System.exit(0);
}
};
public void foo(){
this.foo(DEFAULT_ACTION);
}
/* package-visible only for unit testing */
void foo(Runnable action){
// ...some stuff...
action.run();
}
... и метод тестирования JUnit ...
public void testFoo(){
final AtomicBoolean actionWasCalled = new AtomicBoolean(false);
fooObject.foo(new Runnable(){
public void run(){
actionWasCalled.set(true);
}
});
assertTrue(actionWasCalled.get());
}
Это то, что они называют внедрением зависимостей?
Этот пример в том виде, в каком он написан, представляет собой своего рода наполовину запеченную инъекцию зависимости - зависимость передается в видимый для пакета метод foo (либо общедоступным методом foo, либо модульным тестом), но основной класс по-прежнему жестко кодирует реализацию Runnable по умолчанию.
Вы можете использовать java SecurityManager, чтобы предотвратить завершение работы виртуальной машины Java текущим потоком. Следующий код должен делать то, что вы хотите:
SecurityManager securityManager = new SecurityManager() {
public void checkPermission(Permission permission) {
if ("exitVM".equals(permission.getName())) {
throw new SecurityException("System.exit attempted and blocked.");
}
}
};
System.setSecurityManager(securityManager);
Хм. В документации System.exit конкретно указано, что будет вызываться checkExit (int), а не checkPermission с именем = "exitVM". Интересно, стоит ли отменять и то, и другое?
Имя разрешения на самом деле похоже на exitVM. (Код состояния), то есть exitVM.0 - по крайней мере, в моем недавнем тесте на OSX.
Как насчет внедрения ExitManager в эти методы:
public interface ExitManager {
void exit(int exitCode);
}
public class ExitManagerImpl implements ExitManager {
public void exit(int exitCode) {
System.exit(exitCode);
}
}
public class ExitManagerMock implements ExitManager {
public bool exitWasCalled;
public int exitCode;
public void exit(int exitCode) {
exitWasCalled = true;
this.exitCode = exitCode;
}
}
public class MethodsCallExit {
public void CallsExit(ExitManager exitManager) {
// whatever
if (foo) {
exitManager.exit(42);
}
// whatever
}
}
Рабочий код использует ExitManagerImpl, а тестовый код использует ExitManagerMock и может проверить, был ли вызван exit () и с каким кодом выхода.
+1 Хорошее решение. Также легко реализовать, если вы используете Spring, поскольку ExitManager становится простым компонентом. Просто имейте в виду, что вам нужно убедиться, что ваш код не продолжает выполняться после вызова exitManager.exit (). Когда ваш код тестируется с помощью фиктивного ExitManager, код фактически не завершается после вызова exitManager.exit.
Мне нравятся некоторые из уже приведенных ответов, но я хотел продемонстрировать другую технику, которая часто бывает полезна при тестировании унаследованного кода. Данный код вроде:
public class Foo {
public void bar(int i) {
if (i < 0) {
System.exit(i);
}
}
}
Вы можете выполнить безопасный рефакторинг, чтобы создать метод, который обертывает вызов System.exit:
public class Foo {
public void bar(int i) {
if (i < 0) {
exit(i);
}
}
void exit(int i) {
System.exit(i);
}
}
Затем вы можете создать подделку для своего теста, которая отменяет выход:
public class TestFoo extends TestCase {
public void testShouldExitWithNegativeNumbers() {
TestFoo foo = new TestFoo();
foo.bar(-1);
assertTrue(foo.exitCalled);
assertEquals(-1, foo.exitValue);
}
private class TestFoo extends Foo {
boolean exitCalled;
int exitValue;
void exit(int i) {
exitCalled = true;
exitValue = i;
}
}
Это общий метод замены поведения тестовыми примерами, и я постоянно использую его при рефакторинге устаревшего кода. Обычно это не то место, где я собираюсь оставить это, а промежуточный шаг для тестирования существующего кода.
Этот метод не останавливает поток управления при вызове exit (). Вместо этого используйте исключение.
Вы фактически может имитируете или игнорируете метод System.exit в тесте JUnit.
Например, используя JMockit, вы можете написать (есть и другие способы):
@Test
public void mockSystemExit(@Mocked("exit") System mockSystem)
{
// Called by code under test:
System.exit(); // will not exit the program
}
Обновлено: альтернативный тест (с использованием последней версии JMockit API), который не позволяет запускать какой-либо код после вызова System.exit(n):
@Test(expected = EOFException.class)
public void checkingForSystemExitWhileNotAllowingCodeToContinueToRun() {
new Expectations(System.class) {{ System.exit(anyInt); result = new EOFException(); }};
// From the code under test:
System.exit(1);
System.out.println("This will never run (and not exit either)");
}
Причина отклонения: проблема с этим решением заключается в том, что если System.exit не является последней строкой в коде (т.е. внутри условия if), код будет продолжать выполняться.
@LeoHolanda Добавил версию теста, которая предотвращает запуск кода после вызова exit (не то чтобы это действительно было проблемой, IMO).
Может быть, было бы более подходящим заменить последний System.out.println () оператором assert?
«@Mocked (« exit »)», похоже, больше не работает с JMockit 1.43: «Значение атрибута не определено для типа аннотации Mocked» Документы: «Есть три различных фиктивных аннотации, которые мы можем использовать при объявлении фиктивных полей и параметры: @Mocked, который будет имитировать все методы и конструкторы на всех существующих и будущих экземплярах имитируемого класса (на время тестов, использующих его); " jmockit.github.io/tutorial/Mocking.html#mocked
Вы действительно пробовали свое предложение? Я получаю: «java.lang.IllegalArgumentException: класс java.lang.System не поддается имитации» Runtime можно смоделировать, но он не останавливает System.exit, по крайней мере, в моем сценарии, в котором выполняются тесты в Gradle.
@ ThorstenSchöning Этот второй тест работает со старыми версиями JMockit; в более новых версиях java.lang.System больше не может быть издевался, но все еще может быть подделанный (с MockUp).
Чтобы ответ VonC работал на JUnit 4, я изменил код следующим образом
protected static class ExitException extends SecurityException {
private static final long serialVersionUID = -1982617086752946683L;
public final int status;
public ExitException(int status) {
super("There is no escape!");
this.status = status;
}
}
private static class NoExitSecurityManager extends SecurityManager {
@Override
public void checkPermission(Permission perm) {
// allow anything.
}
@Override
public void checkPermission(Permission perm, Object context) {
// allow anything.
}
@Override
public void checkExit(int status) {
super.checkExit(status);
throw new ExitException(status);
}
}
private SecurityManager securityManager;
@Before
public void setUp() {
securityManager = System.getSecurityManager();
System.setSecurityManager(new NoExitSecurityManager());
}
@After
public void tearDown() {
System.setSecurityManager(securityManager);
}
Есть среды, в которых возвращаемый код выхода используется вызывающей программой (например, ERRORLEVEL в MS Batch). У нас есть тесты для основных методов, которые делают это в нашем коде, и наш подход заключался в использовании аналогичного переопределения SecurityManager, которое используется в других тестах здесь.
Вчера вечером я собрал небольшой JAR, используя аннотации Junit @Rule, чтобы скрыть код диспетчера безопасности, а также добавить ожидания на основе ожидаемого кода возврата. http://code.google.com/p/junitsystemrules/
Используйте Runtime.exec(String command) для запуска JVM в отдельном процессе.
Как вы будете взаимодействовать с тестируемым классом, если он находится в отдельном процессе от модульного теста?
В 99% случаев System.exit находится внутри основного метода. Следовательно, вы взаимодействуете с ними с помощью аргументов командной строки (т.е. args).
В библиотеке Системная лямбда есть метод catchSystemExit. С помощью этого правила вы можете тестировать код, который вызывает System.exit (...):
public class MyTest {
@Test
public void systemExitWithArbitraryStatusCode() {
SystemLambda.catchSystemExit(() -> {
//the code under test, which calls System.exit(...);
});
}
@Test
public void systemExitWithSelectedStatusCode0() {
int status = SystemLambda.catchSystemExit(() -> {
//the code under test, which calls System.exit(0);
});
assertEquals(0, status);
}
}
Для Java 5–7 в библиотеке Системные правила есть правило JUnit под названием ExpectedSystemExit. С помощью этого правила вы можете протестировать код, который вызывает System.exit (...):
public class MyTest {
@Rule
public final ExpectedSystemExit exit = ExpectedSystemExit.none();
@Test
public void systemExitWithArbitraryStatusCode() {
exit.expectSystemExit();
//the code under test, which calls System.exit(...);
}
@Test
public void systemExitWithSelectedStatusCode0() {
exit.expectSystemExitWithStatus(0);
//the code under test, which calls System.exit(0);
}
}
Полное раскрытие: я являюсь автором обеих библиотек.
Идеальный. Элегантный. Мне не пришлось менять стежок моего исходного кода или экспериментировать с менеджером безопасности. Это должен быть главный ответ!
Это должен быть главный ответ. Вместо бесконечных споров о правильном использовании System.exit, сразу к делу. Кроме того, это гибкое решение, соответствующее самому JUnit, поэтому нам не нужно изобретать велосипед или путаться с менеджером безопасности.
@LeoHolanda. Разве это решение не страдает той же "проблемой", которую вы использовали, чтобы оправдать отрицательный голос за мой ответ? А как насчет пользователей TestNG?
@ Rogério Нет. С ExpectedSystemExit тестируемый код завершится, когда обнаружит имитацию вызова System.exit, но продолжит выполнение других тестов. Кроме того, если exit не вызывается (с ожидаемым кодом выхода), тест завершится неудачно.
@LeoHolanda Вы правы; глядя на реализацию правила, я теперь вижу, что он использует настраиваемый диспетчер безопасности, который выдает исключение при вызове проверки «выхода из системы», тем самым завершая тест. Добавленный пример теста в моем ответе удовлетворяет обоим требованиям (правильная проверка с вызовом / не вызовом System.exit), BTW. Использование ExpectedSystemRule, конечно, приятно; проблема в том, что для этого требуется дополнительная сторонняя библиотека, которая дает очень мало практической пользы и специфична для JUnit.
@Stefan Birkner К сожалению, ExpectedSystemExit некорректно работает в многопоточных тестах.
SystemRules действительно полезен (спасибо за код !!). Единственная проблема в том, что это похоже на конец теста. Мне было интересно, есть ли у вас способ exitRule.ignoreAndContinue (); это позволит моему junit сделать больше проверок после.
Есть exit.checkAssertionAfterwards().
Это работает с Gradle и JUnit Jupiter? Gradle создает новую JVM для тестов, и я всегда получаю сообщение об ошибке: «Возникло непредвиденное исключение. Org.gradle.internal.remote.internal.MessageIOException: не удалось прочитать сообщение из /127.0.0.1:49215». Обеспечение всех зависимостей, таких как junit-vintage-engine и junit:junit:4.12, и всего прочего, но System.exit в созданной JVM, похоже, не игнорируется. junit.org/junit5/docs/current/user-guide/…
это работает безупречно с последней версией junit5! Используйте это вместо Системных правил, из-за которых возникают некоторые проблемы с тестированием кода для выхода из системы.
Я согласен с Эрик Шефер. Но если вы используете хороший фреймворк для имитации, такой как Mockito, достаточно простого конкретного класса, нет необходимости в интерфейсе и двух реализациях.
Проблема:
// do thing1
if (someCondition) {
System.exit(1);
}
// do thing2
System.exit(0)
Поддельный Sytem.exit() не прекращает выполнение. Это плохо, если вы хотите проверить, что thing2 не выполняется.
Решение:
Вы должны реорганизовать этот код, как это предлагает Мартин:
// do thing1
if (someCondition) {
return 1;
}
// do thing2
return 0;
И сделайте System.exit(status) в вызывающей функции. Это заставляет вас иметь все ваши System.exit() в одном месте в main() или рядом с ним. Это чище, чем вызов System.exit() глубоко внутри вашей логики.
Обертка:
public class SystemExit {
public void exit(int status) {
System.exit(status);
}
}
Основной:
public class Main {
private final SystemExit systemExit;
Main(SystemExit systemExit) {
this.systemExit = systemExit;
}
public static void main(String[] args) {
SystemExit aSystemExit = new SystemExit();
Main main = new Main(aSystemExit);
main.executeAndExit(args);
}
void executeAndExit(String[] args) {
int status = execute(args);
systemExit.exit(status);
}
private int execute(String[] args) {
System.out.println("First argument:");
if (args.length == 0) {
return 1;
}
System.out.println(args[0]);
return 0;
}
}
Тестовое задание:
public class MainTest {
private Main main;
private SystemExit systemExit;
@Before
public void setUp() {
systemExit = mock(SystemExit.class);
main = new Main(systemExit);
}
@Test
public void executeCallsSystemExit() {
String[] emptyArgs = {};
// test
main.executeAndExit(emptyArgs);
verify(systemExit).exit(1);
}
}
Есть небольшая проблема с решением SecurityManager. Некоторые методы, такие как JFrame.exitOnClose, также вызывают SecurityManager.checkExit. В моем приложении я не хотел, чтобы этот вызов завершился сбоем, поэтому я использовал
Class[] stack = getClassContext();
if (stack[1] != JFrame.class && !okToExit) throw new ExitException();
super.checkExit(status);
Вы можете протестировать System.exit (..) с заменой экземпляра Runtime. Например. с TestNG + Mockito:
public class ConsoleTest {
/** Original runtime. */
private Runtime originalRuntime;
/** Mocked runtime. */
private Runtime spyRuntime;
@BeforeMethod
public void setUp() {
originalRuntime = Runtime.getRuntime();
spyRuntime = spy(originalRuntime);
// Replace original runtime with a spy (via reflection).
Utils.setField(Runtime.class, "currentRuntime", spyRuntime);
}
@AfterMethod
public void tearDown() {
// Recover original runtime.
Utils.setField(Runtime.class, "currentRuntime", originalRuntime);
}
@Test
public void testSystemExit() {
// Or anything you want as an answer.
doNothing().when(spyRuntime).exit(anyInt());
System.exit(1);
verify(spyRuntime).exit(1);
}
}
Большинство решений будут
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 или любую другую платформу. Единственный элемент, зависящий от фреймворка, не проходит тест. Это можно легко изменить на что-то независимое от фреймворка (кроме Юнит 4Rule
Есть возможности для улучшения, например, перегрузка assertExits() настраиваемыми сообщениями.
Это во многом такая же реализация, как и в System Lambda, и в системных заглушках - github.com/webcompere/system-stubs#systemexit - последний имеет плагин JUnit 5, который можно использовать как универсальное преобразование System.exit в обнаруживаемую ошибку, предотвращая тем самым неожиданные выходы из остановки теста.
Системные заглушки - https://github.com/webcompere/system-stubs - тоже могут решить эту проблему. Он использует синтаксис System Lambda для обертывания кода, который знать будет выполнять System.exit, но это может привести к странным эффектам, когда другой код неожиданно завершает работу.
С помощью плагина JUnit 5 мы можем гарантировать, что любой выход будет преобразован в исключение:
@ExtendWith(SystemStubsExtension.class)
class SystemExitUseCase {
// the presence of this in the test means System.exit becomes an exception
@SystemStub
private SystemExit systemExit;
@Test
void doSomethingThatAccidentallyCallsSystemExit() {
// this test would have stopped the JVM, now it ends in `AbortExecutionException`
// System.exit(1);
}
@Test
void canCatchSystemExit() {
assertThatThrownBy(() -> System.exit(1))
.isInstanceOf(AbortExecutionException.class);
assertThat(systemExit.getExitCode()).isEqualTo(1);
}
}
В качестве альтернативы также можно использовать статический метод, подобный утверждению:
assertThat(catchSystemExit(() -> {
//the code under test
System.exit(123);
})).isEqualTo(123);
Мне любопытно, почему функция когда-либо вызывала System.exit () ...