【问题标题】:Dealing with System.exit(0) in JUnit tests在 JUnit 测试中处理 System.exit(0)
【发布时间】:2011-05-26 15:40:57
【问题描述】:

我正在为现有的 Java Swing 应用程序实现一些测试,这样我就可以安全地重构和扩展代码而不会破坏任何东西。我从 JUnit 中的一些单元测试开始,因为这似乎是最简单的开始方式,但现在我的首要任务是创建一些端到端测试来作为一个整体来运行应用程序。

我在每个测试中重新启动应用程序,方法是将每个测试方法放在一个单独的测试用例中,并在 Ant 的 junit 任务中使用 fork="yes" 选项。但是,我想在测试中实现的一些用例涉及用户退出应用程序,这会导致调用 System.exit(0) 的方法之一。这被 JUnit 视为错误:junit.framework.AssertionFailedError: Forked Java VM exited abnormally

有没有办法告诉 JUnit 以零返回码退出实际上是可以的?

【问题讨论】:

标签: java junit


【解决方案1】:

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(...)
         * with an arbitrary status
         */
    }

    @Test
    public void systemExitWithSelectedStatusCode0() {
        exit.expectSystemExitWithStatus(0);
        //the code under test, which calls System.exit(0)
    }
}

系统规则至少需要 JUnit 4.9。

完全披露:我是系统规则的作者。

【讨论】:

【解决方案2】:

我的处理方法是安装一个安全管理器,它在调用 System.exit 时会引发异常。然后有代码可以捕获异常并且不会通过测试。

public class NoExitSecurityManager
    extends java.rmi.RMISecurityManager
{
    private final SecurityManager parent;

    public NoExitSecurityManager(final SecurityManager manager)
    {
        parent = manager;
    }

    public void checkExit(int status)
    {
        throw new AttemptToExitException(status);
    }

    public void checkPermission(Permission perm)
    {
    }
}

然后在代码中,类似:

catch(final Throwable ex)
{
    final Throwable cause;

    if(ex.getCause() == null)
    {
        cause = ex;
    }
    else
    {
        cause = ex.getCause();
    }

    if(cause instanceof AttemptToExitException)
    {
        status = ((AttemptToExitException)cause).getStatus();
    }
    else
    {
        throw cause;
    }
}

assertEquals("System.exit must be called with the value of " + expectedStatus, expectedStatus, status);

【讨论】:

  • 这行得通,而且 FEST-Swing 甚至提供了自己的 NoExitSecurityManager。我认为这将是我目前的方法,因为它不涉及对现有应用程序代码的更改。唯一的缺点是应用程序有一个 UncaughtExceptionHandler,当安全管理器阻止退出调用时,它会弹出一个错误报告对话框。测试仍然通过,但任何观看测试的人都可能会觉得应用程序已崩溃。
【解决方案3】:

您能否将“系统退出”抽象为一个新的依赖项,以便在您的测试中您可以只使用一个伪造的记录退出已被调用的事实(和值),但使用调用 @ 987654321@在实际应用中?

【讨论】:

  • 这是可能的。我希望在在进行任何更改之前尽可能多地使用测试套件,但我想有时您只需要潜入并进行一些仔细的更改即可使测试正常工作。
  • @Ben:希望在这种情况下,这将是一个相对较小的变化——我肯定更愿意这样做,而不是与安全经理混在一起。当然,您还需要确保应用程序的 Swing 线程正确终止等。
  • 查看代码,这可能比我现在有时间更复杂,但无疑是一个很好的练习,可以应用“有效地使用遗留代码”中的课程。我不确定如何让 Swing 线程正确终止:只需处理打开的 Swing 窗口?在这个阶段,为每个测试派生一个新的 VM 似乎更安全,这就是为什么我被限制为每个班级一个测试。不过,我很高兴收到更好的建议。
  • @Ben:我相信关闭所有打开的窗口就可以了,是的。很久以前没有,但我相信现在有。
【解决方案4】:

如果有人需要 JUnit 5 的这个功能,我已经 written an extension 来做这件事。这是一个简单的注释,您可以使用它来告诉您的测试用例期待并退出状态代码或特定的退出状态代码。

例如,任何退出代码都可以:

public class MyTestCases { 

    @Test
    @ExpectSystemExit
    public void thatSystemExitIsCalled() {
        System.exit(1);
    }
}

如果我们要查找特定代码:

public class MyTestCases {

    @Test
    @ExpectSystemExitWithStatus(1)
    public void thatSystemExitIsCalled() {
        System.exit(1);
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-03-04
    • 1970-01-01
    • 2015-01-19
    • 1970-01-01
    • 2016-01-27
    • 1970-01-01
    • 2011-02-26
    • 1970-01-01
    相关资源
    最近更新 更多