大多数解决方案将
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()
可定制消息的重载。