【问题标题】:Java: How to test methods that call System.exit()?Java:如何测试调用 System.exit() 的方法?
【发布时间】:2010-09-23 11:45:27
【问题描述】:

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

[编辑] 有问题的类实际上是一个命令行工具,我试图在 JUnit 中对其进行测试。也许 JUnit 根本不适合这项工作?欢迎提出补充回归测试工具的建议(最好是与 JUnit 和 EclEmma 很好集成的工具)。

【问题讨论】:

  • 我很好奇为什么函数会调用 System.exit()...
  • 如果您正在调用退出应用程序的函数。例如,如果用户尝试执行一项他们无权连续执行超过 x 次的任务,您会强制他们退出应用程序。
  • 我仍然认为在这种情况下,应该有更好的方式从应用程序返回而不是 System.exit()。
  • 如果您正在测试 main(),那么调用 System.exit() 是非常有意义的。我们有一个要求,一个批处理应该在错误时以 1 退出,在成功时以 0 退出。
  • 我不同意所有说System.exit() 不好的人——你的程序应该很快就会失败。在开发人员想要退出并给出虚假错误的情况下,抛出异常只会延长应用程序处于无效状态。

标签: java multithreading unit-testing junit testability


【解决方案1】:

确实,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 月更新:

Will 使用System Rules 提出in the comments,这是一组用于测试使用java.lang.System 的代码的JUnit(4.9+) 规则。
这最初是由 Stefan Birkner 在 2011 年 12 月的 his answer 中提到的。

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

【讨论】:

  • 不喜欢第一个答案,但第二个很酷——我没有惹恼安全经理,并认为他们比这更复杂。但是,如何测试安全管理器/测试机制。
  • 确保teardown正确执行,否则你的测试将在Eclipse等运行器中失败,因为JUnit应用程序无法退出! :)
  • 我不喜欢使用安全管理器的解决方案。对我来说似乎只是为了测试它。
  • 如果您使用的是 junit 4.7 或更高版本,请让库处理捕获您的 System.exit 调用。系统规则 - stefanbirkner.github.com/system-rules
  • "与其以 System.exit(whateverValue) 结束,不如抛出一个未经检查的异常?" -- 因为我使用的是命令行参数处理框架,只要提供了无效的命令行参数,它就会调用System.exit
【解决方案2】:

System Lambda 有一个方法catchSystemExit。使用此规则,您可以测试调用System.exit(...) 的代码:

public class MyTest {
    @Test
    public void systemExitWithArbitraryStatusCode() {
        SystemLambda.catchSystemExit(() -> {
            //the code under test, which calls System.exit(...);
        });
    }


    @Test
    public void systemExitWithSelectedStatusCode0() {
        int status = SystemLambda.catchSystemExit(() -> {
            //the code under test, which calls System.exit(0);
        });

        assertEquals(0, status);
    }
}

对于 Java 5 到 7,库 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(...);
    }

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

完全披露:我是这两个库的作者。

【讨论】:

  • 完美。优雅的。不必更改我的原始代码或与安全管理器一起玩。这应该是最佳答案!
  • 这应该是最佳答案。与其对 System.exit 的正确使用进行无休止的辩论,不如直奔主题。此外,它是一个遵循 JUnit 本身的灵活解决方案,因此我们不需要重新发明轮子或搞乱安全管理器。
  • @LeoHolanda 这个解决方案不会遇到您用来证明对我的答案投反对票的同样“问题”吗?另外,TestNG 用户呢?
  • @LeoHolanda 你说得对;查看规则的实现,我现在看到它使用了一个自定义安全管理器,当调用“系统退出”检查时会引发异常,从而结束测试。我的答案中添加的示例测试满足了这两个要求(使用 System.exit 进行正确验证被调用/不被调用),顺便说一句。当然,ExpectedSystemRule 的使用很好;问题是它需要一个额外的 3rd-party 库,该库在现实世界的实用性方面提供的很少,并且是特定于 JUnit 的。
  • exit.checkAssertionAfterwards()
【解决方案3】:

如何将“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()以及使用哪个退出代码。

【讨论】:

  • +1 不错的解决方案。如果您使用 Spring 也很容易实现,因为 ExitManager 成为一个简单的组件。请注意,您需要确保您的代码在 exitManager.exit() 调用之后不会继续执行。使用模拟 ExitManager 测试您的代码时,调用 exitManager.exit 后代码实际上不会退出。
【解决方案4】:

您实际上可以在 JUnit 测试中模拟或存根 System.exit 方法。

例如,使用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条件里面),代码会继续运行。
  • @LeoHolanda 添加了一个测试版本,该版本会阻止代码在exit 调用之后运行(这并不是真正的问题,IMO)。
  • 用断言语句替换最后一个 System.out.println() 是否更合适?
  • "@Mocked("exit")" 似乎不再适用于 JMockit 1.43:“未定义注解类型 Mocked 的属性值” 文档:“存在三种不同的模拟注解我们可以在声明模拟字段和参数时使用:@Mocked,它将模拟模拟类的所有现有和未来实例上的所有方法和构造函数(在使用它的测试期间);" jmockit.github.io/tutorial/Mocking.html#mocked
  • 你真的尝试过你的建议吗?我得到:“java.lang.IllegalArgumentException: Class java.lang.System is not mockable”Runtime 可以被模拟,但至少在我在 Gradle 中运行测试的场景中不会停止 System.exit
【解决方案5】:

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

【讨论】:

  • 这就是他们所说的依赖注入吗?
  • 这个编写的例子是一种半生不熟的依赖注入——依赖被传递给包可见的 foo 方法(通过公共 foo 方法或单元测试),但主类仍然硬编码默认的 Runnable 实现。
【解决方案6】:

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

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() 时不会停止控制流程。请改用异常。
【解决方案7】:

为了让 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);
}

【讨论】:

    【解决方案8】:

    创建一个封装 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);
        }
    }
    

    【讨论】:

      【解决方案9】:

      快速查看 api,显示 System.exit 可以抛出异常,尤其是。如果安全管理器禁止关闭虚拟机。也许解决方案是安装这样的管理器。

      【讨论】:

        【解决方案10】:

        您可以使用 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);
        

        【讨论】:

        • 嗯。 System.exit 文档明确表示将调用 checkExit(int),而不是 name="exitVM" 的 checkPermission。我想知道我是否应该同时覆盖两者?
        • 权限名称实际上似乎是 exitVM.(statuscode),即 exitVM.0 - 至少在我最近在 OSX 上的测试中。
        【解决方案11】:

        您可以通过替换运行时实例来测试 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);
            }
        }
        

        【讨论】:

          【解决方案12】:

          在某些环境中,调用程序会使用返回的退出代码(例如 MS Batch 中的 ERRORLEVEL)。我们在代码中对执行此操作的主要方法进行了测试,我们的方法是使用与此处其他测试中使用的类似的 SecurityManager 覆盖。

          昨晚我整理了一个小 JAR,使用 Junit @Rule 注释来隐藏安全管理器代码,并根据预期返回代码添加预期。 http://code.google.com/p/junitsystemrules/

          【讨论】:

            【解决方案13】:

            大多数解决方案都会

            • 在调用 System.exit() 时终止测试(方法,而不是整个运行)
            • 忽略已安装的SecurityManager
            • 有时非常特定于测试框架
            • 限制每个测试用例最多使用一次

            因此,大多数解决方案不适合以下情况:

            • 调用System.exit() 后将执行副作用验证
            • 现有的安全管理器是测试的一部分。
            • 使用了不同的测试框架。
            • 您希望在单个测试用例中进行多次验证。这可能是严格不推荐的,但有时会非常方便,尤其是与 assertAll() 结合使用时。

            我对其他答案中提出的现有解决方案施加的限制不满意,因此我自己想出了一些办法。

            下面的类提供了一个方法assertExits(int expectedStatus, Executable executable),它断言System.exit()是用指定的status值调用的,并且可以在它之后继续测试。它的工作方式与 JUnit 5 assertThrows 相同。它还尊重现有的安全经理。

            还有一个问题:当被测代码安装一个新的安全管理器时,它完全取代了测试设置的安全管理器。我知道的所有其他基于SecurityManager 的解决方案都会遇到同样的问题。

            import java.security.Permission;
            
            import static java.lang.System.getSecurityManager;
            import static java.lang.System.setSecurityManager;
            import static org.junit.jupiter.api.Assertions.assertEquals;
            import static org.junit.jupiter.api.Assertions.fail;
            
            public enum ExitAssertions {
                ;
            
                public static <E extends Throwable> void assertExits(final int expectedStatus, final ThrowingExecutable<E> executable) throws E {
                    final SecurityManager originalSecurityManager = getSecurityManager();
                    setSecurityManager(new SecurityManager() {
                        @Override
                        public void checkPermission(final Permission perm) {
                            if (originalSecurityManager != null)
                                originalSecurityManager.checkPermission(perm);
                        }
            
                        @Override
                        public void checkPermission(final Permission perm, final Object context) {
                            if (originalSecurityManager != null)
                                originalSecurityManager.checkPermission(perm, context);
                        }
            
                        @Override
                        public void checkExit(final int status) {
                            super.checkExit(status);
                            throw new ExitException(status);
                        }
                    });
                    try {
                        executable.run();
                        fail("Expected System.exit(" + expectedStatus + ") to be called, but it wasn't called.");
                    } catch (final ExitException e) {
                        assertEquals(expectedStatus, e.status, "Wrong System.exit() status.");
                    } finally {
                        setSecurityManager(originalSecurityManager);
                    }
                }
            
                public interface ThrowingExecutable<E extends Throwable> {
                    void run() throws E;
                }
            
                private static class ExitException extends SecurityException {
                    final int status;
            
                    private ExitException(final int status) {
                        this.status = status;
                    }
                }
            }
            

            你可以像这样使用这个类:

                @Test
                void example() {
                    assertExits(0, () -> System.exit(0)); // succeeds
                    assertExits(1, () -> System.exit(1)); // succeeds
                    assertExits(2, () -> System.exit(1)); // fails
                }
            

            如有必要,可以轻松地将代码移植到 JUnit 4、TestNG 或任何其他框架。唯一特定于框架的元素是未通过测试。这可以很容易地更改为独立于框架的东西(除了 Junit 4 Rule

            还有改进的余地,例如,用可自定义的消息重载assertExits()

            【讨论】:

            • 这与 System Lambda 和 System Stubs 使用的实现大致相同 - github.com/webcompere/system-stubs#systemexit - 后者有一个 JUnit 5 插件,可用作将 System.exit 通用转换为可捕获错误,从而阻止意外退出停止测试。
            【解决方案14】:

            系统存根 - https://github.com/webcompere/system-stubs - 也能够解决这个问题。它共享 System Lambda 的语法来包装我们知道将执行 System.exit 的代码,但是当其他代码意外退出时,这可能会导致奇怪的效果。

            通过 JUnit 5 插件,我们可以确保任何退出都将转换为异常:

            @ExtendWith(SystemStubsExtension.class)
            class SystemExitUseCase {
                // the presence of this in the test means System.exit becomes an exception
                @SystemStub
                private SystemExit systemExit;
            
                @Test
                void doSomethingThatAccidentallyCallsSystemExit() {
                    // this test would have stopped the JVM, now it ends in `AbortExecutionException`
                    // System.exit(1);
                }
            
                @Test
                void canCatchSystemExit() {
                    assertThatThrownBy(() -> System.exit(1))
                        .isInstanceOf(AbortExecutionException.class);
            
                    assertThat(systemExit.getExitCode()).isEqualTo(1);
                }
            }
            

            或者,也可以使用类断言的静态方法:

            assertThat(catchSystemExit(() -> {
               //the code under test
               System.exit(123);
            })).isEqualTo(123);
            

            【讨论】:

              【解决方案15】:

              使用Runtime.exec(String command) 在单独的进程中启动JVM。

              【讨论】:

              • 如果被测类在与单元测试不同的进程中,你将如何与它进行交互?
              • 在 99% 的情况下 System.exit 在 main 方法中。因此,您可以通过命令行参数(即 args)与它们进行交互。
              【解决方案16】:

              SecurityManager 解决方案存在一个小问题。一些方法,如JFrame.exitOnClose,也调用SecurityManager.checkExit。在我的应用程序中,我不希望调用失败,所以我使用了

              Class[] stack = getClassContext();
              if (stack[1] != JFrame.class && !okToExit) throw new ExitException();
              super.checkExit(status);
              

              【讨论】:

                【解决方案17】:

                可用于单元和集成测试的一种普遍有用的方法是拥有一个包私有(默认访问)可模拟运行器类,它提供 run() 和 exit() 方法。这些方法可以被测试模块中的 Mock 或 Fake 测试类覆盖。

                测试类(JUnit 或其他)提供了 exit() 方法可以代替 System.exit() 抛出的异常。

                package mainmocked;
                class MainRunner {
                    void run(final String[] args) {
                        new MainMocked().run(args);    
                    }
                    void exit(final int status) {
                        System.exit(status);
                    }
                }
                

                下面带有 main() 的类,也有一个 altMain() 用于在单元或集成测试时接收 mock 或 fake runner:

                package mainmocked;
                
                public class MainMocked {
                    private static MainRunner runner = new MainRunner();
                
                    static void altMain(final String[] args, final MainRunner inRunner) {
                        runner = inRunner;
                        main(args);
                    }
                
                    public static void main(String[] args) {
                        try {
                          runner.run(args);
                        } catch (Throwable ex) {
                            // Log("error: ", ex);
                            runner.exit(1);
                        }
                        runner.exit(0);
                    } // main
                
                
                    public void run(String[] args) {
                        // do things ...
                    }
                } // class
                

                一个简单的模拟(使用 Mockito)将是:

                @Test
                public void testAltMain() {
                    String[] args0 = {};
                    MainRunner mockRunner = mock(MainRunner.class);
                    MainMocked.altMain(args0, mockRunner);
                
                    verify(mockRunner).run(args0);
                    verify(mockRunner).exit(0);
                  }
                

                更复杂的测试类会使用 Fake,其中 run() 可以做任何事情,并使用 Exception 类来替换 System.exit():

                private class FakeRunnerRuns extends MainRunner {
                    @Override
                    void run(String[] args){
                        new MainMocked().run(args);
                    }
                    @Override
                    void exit(final int status) {
                        if (status == 0) {
                            throw new MyMockExitExceptionOK("exit(0) success");
                        }
                        else {
                            throw new MyMockExitExceptionFail("Unexpected Exception");
                        } // ok
                    } // exit
                } // class
                

                【讨论】:

                  【解决方案18】:

                  调用 System.exit() 是一种不好的做法,除非它是在 main() 中完成的。这些方法应该抛出一个异常,最终被你的 main() 捕获,然后用适当的代码调用 System.exit。

                  【讨论】:

                  • 不过,这并不能回答问题。如果被测试的功能最终是主要方法怎么办?因此,从设计的角度来看,调用 System.exit() 可能是有效且可以的。你如何为它写一个测试用例?
                  • 你不应该测试主方法,因为主方法应该只接受任何参数,将它们传递给解析器方法,然后启动应用程序。要测试的main方法中应该没有逻辑。
                  • @Elie:在这些类型的问题中有两个有效答案。一个回答提出的问题,一个问为什么这个问题是基于的。两种类型的答案都可以更好地理解,尤其是两者一起使用。
                  【解决方案19】:

                  这里的另一种技术是将附加代码引入(希望少数)逻辑执行 System.exit() 的地方。当逻辑作为单元测试的一部分被调用时,这个附加代码避免执行 System.exit()。例如,定义一个包私有常量,如 TEST_MODE,它通常为 false。然后让单元测试代码将此设置为 true 并向被测代码添加逻辑以检查该情况并绕过 System.exit 调用,而是抛出单元测试逻辑可以捕获的异常。顺便说一句,在 2021 年,使用像 spotbugs 之类的东西,这个问题可以表现在“java.io.IOException:现有连接被远程主机强行关闭”这个晦涩的错误中。

                  【讨论】:

                    猜你喜欢
                    • 1970-01-01
                    • 2011-09-14
                    • 1970-01-01
                    • 2022-01-05
                    • 1970-01-01
                    • 1970-01-01
                    • 2013-11-16
                    • 1970-01-01
                    相关资源
                    最近更新 更多