我有一些方法,应该在某些输入上调用System.exit()。不幸的是,测试这些用例会导致JUnit终止!将方法调用放在新线程中似乎没有帮助,因为System.exit()终止JVM,而不仅仅是当前线程。有什么共同的模式来处理这个问题吗?例如,我可以用存根代替System.exit()吗?
[编辑]所讨论的类实际上是一个命令行工具,我试图在JUnit中测试它。也许JUnit根本不是适合这项工作的工具?欢迎对补充回归测试工具提出建议(最好能与JUnit和EclEmma很好地集成)。
我有一些方法,应该在某些输入上调用System.exit()。不幸的是,测试这些用例会导致JUnit终止!将方法调用放在新线程中似乎没有帮助,因为System.exit()终止JVM,而不仅仅是当前线程。有什么共同的模式来处理这个问题吗?例如,我可以用存根代替System.exit()吗?
[编辑]所讨论的类实际上是一个命令行工具,我试图在JUnit中测试它。也许JUnit根本不是适合这项工作的工具?欢迎对补充回归测试工具提出建议(最好能与JUnit和EclEmma很好地集成)。
当前回答
标准库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);
}
}
完全披露:我是这两个库的作者。
其他回答
实际上,您可以模拟或排除该系统。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)");
}
Derkeiler.com网站建议:
为什么是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月更新:
Will在评论中建议使用系统规则,这是JUnit(4.9+)规则的集合,用于测试使用java.lang.System. Rules的代码。 Stefan Birkner最初在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);
}
}
大多数解决方案都会
在System.exit()被调用时终止测试(方法,而不是整个运行) 忽略已经安装的SecurityManager 有时是非常特定于测试框架的 限制每个测试用例使用Max一次
因此,大多数解决方案不适合以下情况:
在调用System.exit()之后执行副作用的验证 现有的安全管理器是测试的一部分。 使用了不同的测试框架。 您希望在单个测试用例中有多个验证。严格来说,不建议这样做,但有时非常方便,特别是与assertAll()结合使用时。
我不满意其他答案中提出的现有解决方案所施加的限制,因此我自己想出了一些东西。
下面的类提供了一个方法assertExits(int expectedStatus, Executable Executable可执行文件),它断言System.exit()是用指定的状态值调用的,并且测试可以在它之后继续。它的工作方式与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或任何其他框架。唯一特定于框架的元素没有通过测试。这可以很容易地更改为与框架无关的内容(而不是Junit 4规则)
还有改进的空间,例如,使用可定制的消息重载assertExits()。
使用运行时。exec(字符串命令)在单独的进程中启动JVM。
在某些环境中,返回的退出代码由调用程序使用(例如MS批处理中的ERRORLEVEL)。我们对代码中执行此操作的主要方法进行了测试,我们的方法是使用类似于在这里的其他测试中使用的SecurityManager重写。
昨晚,我使用Junit @Rule注释组合了一个小JAR,以隐藏安全管理器代码,并根据预期的返回代码添加期望。http://code.google.com/p/junitsystemrules/