当前位置:  开发笔记 > 编程语言 > 正文

Java:如何测试调用System.exit()的方法?

如何解决《Java:如何测试调用System.exit()的方法?》经验,为你挑选了7个好方法。

我有一些方法可以调用System.exit()某些输入.不幸的是,测试这些情况会导致JUnit终止!将方法调用放在新线程中似乎没有帮助,因为System.exit()终止JVM,而不仅仅是当前线程.是否有任何常见的处理方式?例如,我可以替换存根System.exit()吗?

[编辑]有问题的类实际上是一个命令行工具,我试图在JUnit中测试.也许JUnit根本不适合这份工作?建议使用补充回归测试工具(最好是与JUnit和EclEmma完美集成的东西).



1> VonC..:

的确,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);
    }
}


确保拆除正确执行,否则,您的测试将在Eclipse等运行器中失败,因为JUnit应用程序无法退出!:)
我不喜欢使用安全管理器的解决方案.对我来说似乎只是为了测试它.
如果您使用的是junit 4.7或更高版本,请让库处理捕获您的System.exit调用.系统规则 - http://stefanbirkner.github.com/system-rules/
"而不是使用System.exit(whateverValue)终止,为什么不抛出未经检查的异常?" - 因为我正在使用命令行参数处理框架,只要提供了无效的命令行参数,它就会调用`System.exit`.
不喜欢第一个答案,但第二个答案非常酷 - 我没有与安全经理混淆,并认为他们比这更复杂.但是,您如何测试安全管理器/测试机制.
正是我在寻找好的答案.
投票:不喜欢第一点.我同意System.exit不是一个好习惯,但不是问题的答案.不喜欢第二点.搞乱java安全管理器与单元测试不一致.我喜欢第三点,但它只是来自Stefan Birkner的复制和粘贴答案如下.他的回答应该是被接受的,而不是这个.

2> Stefan Birkn..:

库系统规则库有一个名为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);
    }
}

完全披露:我是该图书馆的作者.


完善.优雅.无需更改原始代码的针脚或使用安全管理器.这应该是最好的答案!
哇这正是我想要的,谢谢!
这应该是最佳答案。直截了当,而不是就如何正确使用System.exit进行无休止的辩论。另外,这是一种灵活的解决方案,可以遵循JUnit本身,因此我们不需要重新发明轮子,也不必搞砸安全管理器。
@LeoHolanda你是对的;查看规则的实现,我现在看到它使用了自定义安全管理器,该安全管理器在调用“系统出口”检查时引发了异常,因此结束了测试。我的答案中添加的示例测试满足了BTW的两个要求(使用System.exit被调用/未调用进行正确的验证)。当然,使用ExpectedSystemRule很好。问题在于它需要一个额外的第三方库,该库在实际使用方面几乎没有提供,并且是特定于JUnit的。
有`exit.checkAssertionAfterwards()`。

3> EricSchaefer..:

如何在此方法中注入"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()以及退出代码.


我非常喜欢这个解决方案.

4> Rogério..:

实际上,您可以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不是代码中的最后一行(即if if condition),代码将继续运行.

5> Scott Bale..:

我们在代码库中使用的一个技巧是将对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()); 
}


这个编写的示例是一种半生不熟的依赖注入 - 依赖关系被传递给package-visible foo方法(通过public foo方法或单元测试),但主类仍然硬编码默认的Runnable实现.

6> Arend v. Rei..:

创建一个包装System.exit()的可模拟类

我同意EricSchaefer.但是如果你使用像Mockito这样的好的模拟框架,一个简单的具体类就足够了,不需要一个接口和两个实现.

在System.exit()上停止测试执行

问题:

// 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);
    }
}



7> Jeffrey Fred..:

我喜欢已经给出的一些答案,但我想展示一种在获得测试遗留代码时通常很有用的不同技术.给出如下代码:

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;
    }
}

这是用于替换行为的测试用例的通用技术,我在重构遗留代码时一直使用它.它通常不是我要离开的地方,而是获得现有代码的中间步骤.


当调用exit()时,此tecniques不会停止控制流.请改用异常.
推荐阅读
夏晶阳--艺术
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有