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

[编辑]所讨论的类实际上是一个命令行工具,我试图在JUnit中测试它。也许JUnit根本不是适合这项工作的工具?欢迎对补充回归测试工具提出建议(最好能与JUnit和EclEmma很好地集成)。


当前回答

你可以用替换Runtime实例来测试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);
    }
}

其他回答

为了让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);
}

快速查看api,可以看到System。Exit可以抛出异常,特别是当securitymanager禁止关闭vm时。也许一个解决方案是安装这样一个管理器。

你可以用替换Runtime实例来测试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);
    }
}

实际上,您可以模拟或排除该系统。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 Lambda有一个方法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,库System Rules有一个叫做ExpectedSystemExit的JUnit规则。使用这个规则,你可以测试调用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);
    }
}

完全披露:我是这两个库的作者。