我有一些方法,应该在某些输入上调用System.exit()。不幸的是,测试这些用例会导致JUnit终止!将方法调用放在新线程中似乎没有帮助,因为System.exit()终止JVM,而不仅仅是当前线程。有什么共同的模式来处理这个问题吗?例如,我可以用存根代替System.exit()吗?
[编辑]所讨论的类实际上是一个命令行工具,我试图在JUnit中测试它。也许JUnit根本不是适合这项工作的工具?欢迎对补充回归测试工具提出建议(最好能与JUnit和EclEmma很好地集成)。
我有一些方法,应该在某些输入上调用System.exit()。不幸的是,测试这些用例会导致JUnit终止!将方法调用放在新线程中似乎没有帮助,因为System.exit()终止JVM,而不仅仅是当前线程。有什么共同的模式来处理这个问题吗?例如,我可以用存根代替System.exit()吗?
[编辑]所讨论的类实际上是一个命令行工具,我试图在JUnit中测试它。也许JUnit根本不是适合这项工作的工具?欢迎对补充回归测试工具提出建议(最好能与JUnit和EclEmma很好地集成)。
当前回答
如何注入一个“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()以及使用哪个退出代码。
其他回答
你可以用替换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);
}
调用System.exit()是一种糟糕的做法,除非它是在main()中完成的。这些方法应该抛出一个异常,最终由main()捕获,然后调用System。使用适当的代码退出。
SecurityManager解决方案有一个小问题。一些方法,如JFrame。exitOnClose,也调用SecurityManager.checkExit。在我的应用程序中,我不希望这个调用失败,所以我使用了
Class[] stack = getClassContext();
if (stack[1] != JFrame.class && !okToExit) throw new ExitException();
super.checkExit(status);
在某些环境中,返回的退出代码由调用程序使用(例如MS批处理中的ERRORLEVEL)。我们对代码中执行此操作的主要方法进行了测试,我们的方法是使用类似于在这里的其他测试中使用的SecurityManager重写。
昨晚,我使用Junit @Rule注释组合了一个小JAR,以隐藏安全管理器代码,并根据预期的返回代码添加期望。http://code.google.com/p/junitsystemrules/