Java:как проверить методы, которые вызывают систему.выход()?
у меня есть несколько методов, которые следует называть System.exit() на некоторых входах. К сожалению, тестирование этих случаев приводит к прекращению JUnit! Помещение вызовов метода в Новый Поток, похоже, не помогает, так как System.exit() завершает JVM, а не только текущий поток. Существуют ли какие-либо общие шаблоны для решения этой проблемы? Например, можно ли заменить заглушку на System.exit()?
[EDIT] рассматриваемый класс на самом деле является инструментом командной строки, который я пытаюсь протестировать внутри JUnit. Может быть, JUnit просто не является правильным инструментом для работы? Приветствуются предложения по дополнительным инструментам регрессионного тестирования (желательно что-то, что хорошо интегрируется с JUnit и EclEmma).
15 ответов:
действительно,Derkeiler.com предлагает:
- почему
System.exit()?вместо завершения с системой.exit (whateverValue), почему бы не бросить непроверенное исключение? При нормальном использовании он будет дрейфовать до последнего ловца JVM и закрывать ваш скрипт (если вы не решите поймать его где-то по пути, что может быть полезно когда-нибудь).
в сценарии JUnit он будет пойман Структура JUnit, которая сообщит, что такой-то тест провалился и плавно переходим к следующему.
- запретить
System.exit()чтобы фактически выйти из JVM:попробуйте изменить TestCase для запуска с помощью диспетчера безопасности, который предотвращает вызов системы.выход, а затем поймать 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.
Это было первоначально упомянуто Стефан Birkner на ответ в декабре 2011 года.System.exit(…)использовать
ExpectedSystemExitправило, чтобы убедиться в том, чтоSystem.exit(…)называется.
Вы также можете проверить статус выхода.для пример:
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); } }
библиотека Системные Правила имеет правило JUnit под названием ExpectedSystemExit. С помощью этого правила вы можете проверить код, который вызывает систему.выход.(..):
public void 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.например, с помощью JMockit вы могли бы написать (есть и другие способы, а):
@Test public void mockSystemExit(@Mocked("exit") System mockSystem) { // Called by code under test: System.exit(); // will not exit the program }
Изменить: альтернативный тест (с использованием последнего API JMockit), который не позволяет запускать какой-либо код после вызова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)"); }
Как насчет введения "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() и с каким кодом выхода.
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()); }
создайте макет-способный класс, который обертывает систему.exit ()
Я согласен с EricSchaefer. Но если вы используете хорошую насмешливую структуру, такую как Mockito достаточно простого конкретного класса, нет необходимости в интерфейсе и двух реализациях.
остановка выполнения теста в системе.exit ()
:
// 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()s в одном месте в или рядом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 Foo { public void bar(int i) { if (i < 0) { System.exit(i); } } }вы можете сделать безопасный рефакторинг, чтобы создать метод, который обертывает системы.вызов на выход:
public class Foo { public void bar(int i) { if (i < 0) { exit(i); } } void exit(int i) { System.exit(i); } }затем вы можете создать подделку для вашего теста, который переопределяет exit:
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; } }Это общий метод для замены поведения для тестовых случаев, и я использую его все время при рефакторинге устаревшего кода. Это не обычно, где я собираюсь оставить вещь, но промежуточный шаг, чтобы получить существующий код под тестом.
быстрый взгляд на api, показывает, что система.exit может вызвать исключение esp. если securitymanager запрещает завершение работы виртуальной машины. Возможно, решение будет заключаться в установке такого менеджера.
вы можете использовать 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);
для ответа 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). У нас есть тесты вокруг основных методов, которые делают это в нашем коде, и наш подход заключался в использовании аналогичного переопределения SecurityManager, используемого в других тестах здесь.
вчера вечером я собрал небольшую банку, используя аннотации Junit @Rule, чтобы скрыть код менеджера безопасности, а также добавить ожидания на основе ожидаемого кода возврата. http://code.google.com/p/junitsystemrules/
вы можете проверить систему.выход.(.) с заменой экземпляра среды выполнения. Например, с 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); } }
Вызов Системе.exit () - это плохая практика, если она не выполняется внутри main (). Эти методы должны вызывать исключение, которое, в конечном счете, перехватывается вашим main (), который затем вызывает System.выход с соответствующим кодом.
есть небольшая проблема с
SecurityManagerрешение. Некоторые методы, такие какJFrame.exitOnCloseиSecurityManager.checkExit. В моем приложении я не хотел, чтобы этот вызов потерпел неудачу, поэтому я использовалClass[] stack = getClassContext(); if (stack[1] != JFrame.class && !okToExit) throw new ExitException(); super.checkExit(status);
Comments