代码之家  ›  专栏  ›  技术社区  ›  Charles Duffy

Java:如何测试调用Stule.ExITE()的方法?

  •  160
  • Charles Duffy  · 技术社区  · 17 年前

    我有几个方法应该调用 System.exit() 在某些输入上。不幸的是,测试这些案例会导致JUnit终止!将方法调用放入新线程似乎没有帮助,因为 系统。 终止JVM,而不仅仅是当前线程。有什么共同的模式来处理这个问题吗?例如,我可以为 系统。 ?

    [编辑]所讨论的类实际上是一个命令行工具,我正试图在JUnit内部测试它。或许朱尼特根本不是合适的工作工具?欢迎对补充回归测试工具的建议(最好是与JUnit和Eclema很好地集成的工具)。

    15 回复  |  直到 7 年前
        1
  •  189
  •   Community Mohan Dere    8 年前

    的确, Derkeiler.com

    • 为什么? System.exit() ?

    为什么不抛出未检查的异常,而不是以System.Exit(WhateVerValue)终止?在正常使用中,它会一直漂移到JVM的最后一个沟渠捕捉器,并关闭您的脚本(除非您决定在途中的某个地方捕捉它,这可能有一天会有用)。

    在JUnit场景中,它将被JUnit框架捕获,该框架将报告 这样那样的试验失败了,顺利地进行下去。

    • 预防

    尝试将测试用例修改为使用安全管理器运行,以防止调用System.Exit,然后捕获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年12月更新:

    Will 提议 in the comments 使用 System Rules ,JUnit(4.9+)规则的集合,用于测试使用 java.lang.System .
    最初是由 Stefan Birkner 在里面 his answer 2011年12月。

    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);
        }
    }
    
        2
  •  84
  •   Stefan Birkner    10 年前

    图书馆 System Rules 具有名为ExpectedSystemExit的JUnit规则。使用此规则,您可以测试调用System.Exit(…)的代码:

    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);
        }
    }
    

    完全公开:我是那个图书馆的作者。

        3
  •  31
  •   Rogério    7 年前

    实际上你 可以 模仿或扼杀 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)");
    }
    
        4
  •  29
  •   EricSchaefer    17 年前

    将“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(),以及使用哪个exit代码。

        5
  •  20
  •   Scott Bale    17 年前

    我们在代码库中使用的一个技巧是将对System.exit()的调用封装在一个可运行的impl中,这个方法默认使用。为了进行单元测试,我们设置了一个不同的模拟可运行的。像这样:

    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()); 
    }
    
        6
  •  5
  •   Jeffrey Fredrick    17 年前

    我喜欢已经给出的一些答案,但我想演示一种不同的技术,这种技术在测试遗留代码时通常很有用。给定的代码如下:

    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);
      }
    }
    

    然后您可以为测试创建一个覆盖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;
        }
    }
    

    这是一种替换测试用例行为的通用技术,我在重构遗留代码时一直使用它。它通常不是我要离开的地方,而是一个中间步骤,以使现有的代码接受测试。

        7
  •  5
  •   Community Mohan Dere    8 年前

    创建一个包装System.Exit()的模拟类

    我同意 EricSchaefer . 但是如果你使用一个好的模拟框架 Mockito 一个简单的具体类就足够了,不需要一个接口和两个实现。

    正在停止System.Exit()上的测试执行

    问题:

    // do thing1
    if(someCondition) {
        System.exit(1);
    }
    // do thing2
    System.exit(0)
    

    嘲弄的 Sytem.exit() 不会终止执行。如果你想测试那是很糟糕的 thing2 未执行。

    解决方案:

    您应该按照建议重构此代码 martin :

    // do thing1
    if(someCondition) {
        return 1;
    }
    // do thing2
    return 0;
    

    并且做 System.exit(status) 在调用函数中。这迫使你拥有 System.exit() S在一个地方或附近 main() . 这比打电话干净 系统。 在你的逻辑深处。

    代码

    Wrapper:

    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);
        }
    }
    
        8
  •  3
  •   flolo    17 年前

    快速查看API,可以发现System.Exit会抛出异常,特别是在SecurityManager禁止关闭VM的情况下。也许解决方案是安装这样的管理器。

        9
  •  3
  •   Marc Novakowski    17 年前

    可以使用Java安全管理器来防止当前线程关闭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);
    
        10
  •  3
  •   Jeow Li Huan    16 年前

    为了让VONC的答案在JUnit4上运行,我修改了以下代码

    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);
    }
    
        11
  •  2
  •   Dan Watt    15 年前

    在某些环境中,调用程序使用返回的退出代码(如MS批处理中的错误级别)。在我们的代码中,我们围绕主要方法进行了测试,我们的方法是使用类似的SecurityManager重写,就像在这里的其他测试中使用的一样。

    昨晚,我用junit@rule注释把一个小jar放在一起,以隐藏安全管理器代码,并根据预期的返回代码添加期望值。 http://code.google.com/p/junitsystemrules/

        12
  •  2
  •   ursa    12 年前

    您可以用替换运行时实例来测试System.Exit(..)。 例如,使用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);
        }
    }
    
        13
  •  1
  •   martin    17 年前

    调用System.exit()是一个糟糕的实践,除非它是在main()中完成的。这些方法应该抛出一个异常,最终由main()捕获,然后由main()使用适当的代码调用System.Exit。

        14
  •  1
  •   Cosmin    14 年前

    使用 Runtime.exec(String command) 在单独的进程中启动JVM。

        15
  •  1
  •   cayhorstmann    14 年前

    有个小问题 SecurityManager 解决方案。一些方法,例如 JFrame.exitOnClose 也叫 SecurityManager.checkExit . 在我的应用程序中,我不希望那个调用失败,所以我使用

    Class[] stack = getClassContext();
    if (stack[1] != JFrame.class && !okToExit) throw new ExitException();
    super.checkExit(status);