Java:如何测试调用System.exit()的方法?

问题

我有一些方法应该在某些输入上调用567779278。不幸的是,测试这些情况会导致JUnit终止!将方法调用放在新线程中似乎没有帮助,因为System.exit()终止了JVM,而不仅仅是当前线程。是否有任何常见的处理方式?例如,我可以将存根替换为System.exit()吗?

[编辑]有问题的类实际上是一个命令行工具,我试图在JUnit中测试。也许JUnit根本不适合这份工作?建议使用互补回归测试工具(最好是与JUnit和EclEmma完美集成的东西)。


#1 热门回答(184 赞)

事实上,Derkeiler.comsuggests:

  • 为什么选择System.exit()?

而不是使用System.exit(whateverValue)终止,为什么不抛出未经检查的异常?在正常使用中,它会一直漂移到JVM的最后一个捕获器并关闭你的脚本(除非你决定在途中捕获它,这可能在某一天有用)。在JUnit场景中,它将被JUnit框架捕获,该框架将报告此类测试失败并顺利地移动到下一个。

  • 防止System.exit()实际退出JVM:

尝试修改TestCase以使用安全管理器运行,该管理器阻止调用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月更新:

Willproposesin the commentsusing系统规则,JUnit(4.9)规则的集合,用于测试使用422516276的代码。
这最初是在2011年12月由3840674905inhis answer提到的。

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 热门回答(73 赞)

librarySystem 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 热门回答(30 赞)

你实际上是在JUnit测试中模拟或删除了System.exit方法。

例如,使用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)");
}