我有一些方法可以调用System.exit()
某些输入.不幸的是,测试这些情况会导致JUnit终止!将方法调用放在新线程中似乎没有帮助,因为System.exit()
终止JVM,而不仅仅是当前线程.是否有任何常见的处理方式?例如,我可以替换存根System.exit()
吗?
[编辑]有问题的类实际上是一个命令行工具,我试图在JUnit中测试.也许JUnit根本不适合这份工作?建议使用补充回归测试工具(最好是与JUnit和EclEmma完美集成的东西).
的确,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月更新:
将在评论中使用系统规则提出一系列JUnit(4.9+)规则来测试使用的代码java.lang.System
.
这是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); } }
库系统规则库有一个名为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); } }
完全披露:我是该图书馆的作者.
如何在此方法中注入"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()以及退出代码.
实际上,您可以System.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.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(); }
...和JUnit测试方法......
public void testFoo(){ final AtomicBoolean actionWasCalled = new AtomicBoolean(false); fooObject.foo(new Runnable(){ public void run(){ actionWasCalled.set(true); } }); assertTrue(actionWasCalled.get()); }
我同意EricSchaefer.但是如果你使用像Mockito这样的好的模拟框架,一个简单的具体类就足够了,不需要一个接口和两个实现.
问题:
// do thing1 if(someCondition) { System.exit(1); } // do thing2 System.exit(0)
模拟Sytem.exit()
不会终止执行.如果你想测试thing2
没有执行,这是不好的.
解:
您应该按照martin的建议重构此代码:
// do thing1 if(someCondition) { return 1; } // do thing2 return 0;
并System.exit(status)
在调用函数中执行.这迫使你将所有的System.exit()
s放在一个地方或附近main()
.这比System.exit()
在逻辑内深处调用要干净得多.
包装:
public class SystemExit { public void exit(int status) { System.exit(status); } }
主要:
public class Main { private final SystemExit systemExit; Main(SystemExit systemExit) { this.systemExit = systemExit; } public static void main(String[] args) { SystemExit aSystemExit = new SystemExit(); Main main = new Main(aSystemExit); main.executeAndExit(args); } void executeAndExit(String[] args) { int status = execute(args); systemExit.exit(status); } private int execute(String[] args) { System.out.println("First argument:"); if (args.length == 0) { return 1; } System.out.println(args[0]); return 0; } }
测试:
public class MainTest { private Main main; private SystemExit systemExit; @Before public void setUp() { systemExit = mock(SystemExit.class); main = new Main(systemExit); } @Test public void executeCallsSystemExit() { String[] emptyArgs = {}; // test main.executeAndExit(emptyArgs); verify(systemExit).exit(1); } }
我喜欢已经给出的一些答案,但我想展示一种在获得测试遗留代码时通常很有用的不同技术.给出如下代码:
public class Foo { public void bar(int i) { if (i < 0) { System.exit(i); } } }
您可以进行安全重构以创建包装System.exit调用的方法:
public class Foo { public void bar(int i) { if (i < 0) { exit(i); } } void exit(int i) { System.exit(i); } }
然后你可以为你的测试创建一个覆盖退出的假货:
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; } }
这是用于替换行为的测试用例的通用技术,我在重构遗留代码时一直使用它.它通常不是我要离开的地方,而是获得现有代码的中间步骤.