Java:如何测试调用 System.exit() 的方法?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/309396/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Java: How to test methods that call System.exit()?
提问by Chris Conway
I've got a few methods that should call System.exit()
on certain inputs. Unfortunately, testing these cases causes JUnit to terminate! Putting the method calls in a new Thread doesn't seem to help, since System.exit()
terminates the JVM, not just the current thread. Are there any common patterns for dealing with this? For example, can I subsitute a stub for System.exit()
?
我有一些方法应该调用System.exit()
某些输入。不幸的是,测试这些情况会导致 JUnit 终止!将方法调用放在新线程中似乎没有帮助,因为会System.exit()
终止 JVM,而不仅仅是当前线程。是否有任何常见的模式来处理这个问题?例如,我可以用存根代替System.exit()
吗?
[EDIT] The class in question is actually a command-line tool which I'm attempting to test inside JUnit. Maybe JUnit is simply not the right tool for the job? Suggestions for complementary regression testing tools are welcome (preferably something that integrates well with JUnit and EclEmma).
[编辑] 有问题的类实际上是一个命令行工具,我试图在 JUnit 中测试它。也许 JUnit 根本不是适合这项工作的工具?欢迎对补充回归测试工具提出建议(最好是与 JUnit 和 EclEmma 集成良好的工具)。
采纳答案by VonC
Indeed, Derkeiler.comsuggests:
事实上,Derkeiler.com建议:
- Why
System.exit()
?
- 为什么
System.exit()
?
Instead of terminating with System.exit(whateverValue), why not throw an unchecked exception? In normal use it will drift all the way out to the JVM's last-ditch catcher and shut your script down (unless you decide to catch it somewhere along the way, which might be useful someday).
In the JUnit scenario it will be caught by the JUnit framework, which will report that such-and-such test failed and move smoothly along to the next.
与其以 System.exit(whateverValue) 终止,为什么不抛出未经检查的异常?在正常使用中,它会一直漂移到 JVM 的最后一个陷阱捕获器并关闭您的脚本(除非您决定在途中的某个地方捕获它,这有一天可能会有用)。
在 JUnit 场景中,它将被 JUnit 框架捕获,它将报告某某测试失败并顺利进行下一个测试。
- Prevent
System.exit()
to actually exit the JVM:
- 防止
System.exit()
实际退出JVM:
Try modifying the TestCase to run with a security manager that prevents calling System.exit, then catch the SecurityException.
尝试修改 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);
}
}
}
Update December 2012:
2012 年 12 月更新:
Willproposes in the commentsusing System Rules, a collection of JUnit(4.9+) rules for testing code which uses java.lang.System
.
This was initially mentioned by Stefan Birknerin his answerin December 2011.
Will在评论中建议使用System Rules,这是一组 JUnit(4.9+) 规则,用于测试使用java.lang.System
. Stefan Birkner在2011 年 12 月的回答中
最初提到了这一点。
System.exit(…)
Use the
ExpectedSystemExit
rule to verify thatSystem.exit(…)
is called.
You could verify the exit status, too.
使用
ExpectedSystemExit
规则来验证System.exit(…)
被调用。
您也可以验证退出状态。
For instance:
例如:
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);
}
}
回答by flolo
A quick look at the api, shows that System.exit can throw an exception esp. if a securitymanager forbids the shutdown of the vm. Maybe a solution would be to install such a manager.
快速查看 api,显示 System.exit 可以抛出异常,尤其是。如果安全经理禁止关闭虚拟机。也许解决方案是安装这样的管理器。
回答by VonC
Calling System.exit() is a bad practice, unless it's done inside a main(). These methods should be throwing an exception which, ultimately, is caught by your main(), who then calls System.exit with the appropriate code.
调用 System.exit() 是一种不好的做法,除非它是在 main() 中完成的。这些方法应该抛出一个异常,最终被 main() 捕获,然后用适当的代码调用 System.exit。
回答by Scott Bale
One trick we used in our code base was to have the call to System.exit() be encapsulated in a Runnable impl, which the method in question used by default. To unit test, we set a different mock Runnable. Something like this:
我们在代码库中使用的一个技巧是将 System.exit() 调用封装在 Runnable impl 中,该方法默认使用该方法。为了进行单元测试,我们设置了一个不同的模拟 Runnable。像这样的东西:
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();
}
...and the JUnit test method...
...和 JUnit 测试方法...
public void testFoo(){
final AtomicBoolean actionWasCalled = new AtomicBoolean(false);
fooObject.foo(new Runnable(){
public void run(){
actionWasCalled.set(true);
}
});
assertTrue(actionWasCalled.get());
}
回答by Marc Novakowski
You can use the java SecurityManager to prevent the current thread from shutting down the Java VM. The following code should do what you want:
您可以使用 java SecurityManager 来防止当前线程关闭 Java VM。下面的代码应该做你想做的:
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);
回答by EricSchaefer
How about injecting an "ExitManager" into this Methods:
如何将“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
}
}
The production code uses the ExitManagerImpl and the test code uses ExitManagerMock and can check if exit() was called and with which exit code.
生产代码使用 ExitManagerImpl,测试代码使用 ExitManagerMock,可以检查是否调用了 exit() 以及使用了哪个退出代码。
回答by Jeffrey Fredrick
I like some of the answers already given but I wanted to demonstrate a different technique that is often useful when getting legacy code under test. Given code like:
我喜欢已经给出的一些答案,但我想展示一种不同的技术,这种技术在测试遗留代码时通常很有用。给定的代码如:
public class Foo {
public void bar(int i) {
if (i < 0) {
System.exit(i);
}
}
}
You can do a safe refactoring to create a method that wraps the System.exit call:
您可以进行安全重构以创建一个包装 System.exit 调用的方法:
public class Foo {
public void bar(int i) {
if (i < 0) {
exit(i);
}
}
void exit(int i) {
System.exit(i);
}
}
Then you can create a fake for your test that overrides 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;
}
}
This is a generic technique for substituting behavior for test cases, and I use it all the time when refactoring legacy code. It not usually where I'm going to leave thing, but an intermediate step to get the existing code under test.
这是一种用行为代替测试用例的通用技术,我在重构遗留代码时一直使用它。它通常不是我要离开的地方,而是让现有代码接受测试的中间步骤。
回答by Rogério
You actually canmock or stub out the System.exit
method, in a JUnit test.
您实际上可以System.exit
在 JUnit 测试中模拟或删除该方法。
For example, using JMockityou could write (there are other ways as well):
例如,您可以使用JMockit编写(还有其他方法):
@Test
public void mockSystemExit(@Mocked("exit") System mockSystem)
{
// Called by code under test:
System.exit(); // will not exit the program
}
EDIT: Alternative test (using latest JMockit API) which does not allow any code to run after a call to System.exit(n)
:
编辑:替代测试(使用最新的 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)");
}
回答by Jeow Li Huan
For VonC's answer to run on JUnit 4, I've modified the code as follows
为了在 JUnit 4 上运行 VonC 的答案,我修改了代码如下
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);
}
回答by Dan Watt
There are environments where the returned exit code is used by the calling program (such as ERRORLEVEL in MS Batch). We have tests around the main methods that do this in our code, and our approach has been to use a similar SecurityManager override as used in other tests here.
在某些环境中,调用程序使用返回的退出代码(例如 MS Batch 中的 ERRORLEVEL)。我们对代码中执行此操作的主要方法进行了测试,我们的方法是使用与此处其他测试中使用的类似的 SecurityManager 覆盖。
Last night I put together a small JAR using Junit @Rule annotations to hide the security manager code, as well as add expectations based on the expected return code. http://code.google.com/p/junitsystemrules/
昨晚我使用 Junit @Rule 注释组合了一个小 JAR 来隐藏安全管理器代码,并根据预期的返回码添加预期。http://code.google.com/p/junitsystemrules/