我们在工作中使用 JUnit 3,并且没有
ExpectedException
注释。我想在我们的代码中添加一个实用程序来包装它:
try {
someCode();
fail("some error message");
} catch (SomeSpecificExceptionType ex) {
}
所以我尝试了这个:
public static class ExpectedExceptionUtility {
public static <T extends Exception> void checkForExpectedException(String message, ExpectedExceptionBlock<T> block) {
try {
block.exceptionThrowingCode();
fail(message);
} catch (T ex) {
}
}
}
但是,我认为 Java 不能在 catch 块中使用通用异常类型。
我怎样才能做这样的事情,绕过 Java 的限制?
有没有办法检查
ex
变量是否属于 T
类型?
您可以传入 Class 对象并以编程方式检查它。
public static <T extends Exception> void checkForException(String message,
Class<T> exceptionType, ExpectedExceptionBlock<T> block) {
try {
block.exceptionThrowingCode();
} catch (Exception ex) {
if ( exceptionType.isInstance(ex) ) {
return;
} else {
throw ex; //optional?
}
}
fail(message);
}
//...
checkForException("Expected an NPE", NullPointerException.class, //...
我不确定您是否想要重新抛出;重新抛出同样会使测试失败/出错,但从语义上讲我不会,因为它基本上意味着“我们没有得到我们期望的异常”,因此这代表了编程错误,而不是测试环境错误。
我理解尝试简化异常测试习惯用法的冲动,但说真的:不要这样做。 你想到的每一个可能的选择都是比疾病更糟糕的治疗方法。 特别是 JUnit 4 的 @ExpectedException 胡说八道! 这是一个过于聪明的框架解决方案,要求每个人都了解它是如何工作的,而不是简单的、不言而喻的常规 Java 代码。更糟糕的是,它使您无法仅包装您期望抛出异常的测试部分,因此,如果较早的设置步骤抛出相同的异常,即使您的代码已损坏,您的测试也会通过。
我可以在这里写一篇关于这个问题的长篇谩骂(很抱歉没有足够的时间),因为我们在 Google 的 Java 工程师中对这个问题进行了长时间的讨论,并且一致认为这些疯狂的事情都不是解决方案是值得的。 习惯尝试/捕捉,其实没那么糟糕。
带有类型参数的 Catch 子句是不可能的:
http://docs.oracle.com/javase/tutorial/java/generics/restrictions.html#cannotCatch
好吧,如果不是预期的异常,您可以捕获异常并重新抛出。尽管良好的编码实践通常规定代码的成功路径不应由异常定义,因此您可能需要重新考虑您的设计。
泛型不是类型。它们不是模板。它们是 Java 中的编译时类型检查。异常块捕获类型。您可以 catch(Exception e) 甚至 catch(Throwable e),然后根据需要进行强制转换。
@FunctionalInterface
public interface ThrowingConsumer<T, E extends Exception> {
void accept(T t) throws E;
}
public static <T, E extends Exception> Consumer<T> errConsumerWrapper(ThrowingConsumer<T, E> throwingConsumer,
Class<E> exceptionClass,
Consumer<E> exceptionConsumer) {
return i -> {
try {
throwingConsumer.accept(i);
} catch (Exception ex) {
try {
exceptionConsumer.accept(exceptionClass.cast(ex));
} catch (ClassCastException ccEx) {
throw new RuntimeException(ex);
}
}
};
}
使用示例
Stream.of("a").forEach(errConsumerWrapper(i -> Integer.parseInt(i), NumberFormatException.class, 可抛出::printStackTrace));
如果您对 OOP+FP 解决此问题的方式感兴趣,有一种特定类型
Either<L, R>
,其中可以通过 Either<L extends Throwable, R>
方法将专业化创建为 .tryCatch()
。
虽然在这个答案中展示
Either<L, R>
的整个实现有点太多(完整记录的实现位于这个Gist中),但下面是一个释义快照,说明了具体化Throwable
的任何后代的样子进入适当的捕获过滤器。
要查看实现细节,请特别关注这对
tryCatch()
方法。这个 StackOverflow Answer 启发了这两种方法中的第一种。
public final class Either<L, R> {
public static <L, R> Either<L, R> left(L value) {
return new Either<>(Optional.of(value), Optional.empty());
}
public static <L, R> Either<L, R> right(R value) {
return new Either<>(Optional.empty(), Optional.of(value));
}
public static <L extends Throwable, R> Either<L, R> tryCatch(
Supplier<R> successSupplier,
Class<L> throwableType
) {
try {
return Either.right(Objects.requireNonNull(successSupplier.get()));
} catch (Throwable throwable) {
if (throwableType.isInstance(throwable)) {
return Either.left((L) throwable);
}
throw throwable;
}
}
public static <R> Either<RuntimeException, R> tryCatch(Supplier<R> successSupplier) {
return tryCatch(successSupplier, RuntimeException.class);
}
private final Optional<L> left;
private final Optional<R> right;
private Either(Optional<L> left, Optional<R> right) {
if (left.isEmpty() == right.isEmpty()) {
throw new IllegalArgumentException("left.isEmpty() must not be equal to right.isEmpty()");
}
this.left = left;
this.right = right;
}
@Override
public boolean equals(Object object) { ... }
@Override
public int hashCode() { ... }
public boolean isLeft() { ... }
public boolean isRight() { ... }
public L getLeft() { ... }
public R getRight() { ... }
public Optional<R> toOptional() { ... }
public <T> Either<L, T> map(Function<? super R, ? extends T> rightFunction) { ... }
public <T> Either<L, T> flatMap(Function<? super R, ? extends Either<L, ? extends T>> rightFunction) { ... }
public <T> Either<T, R> mapLeft(Function<? super L, ? extends T> leftFunction) { ... }
public <T> Either<L, T> mapRight(Function<? super R, ? extends T> rightFunction) { ... }
public <T> Either<T, R> flatMapLeft(Function<? super L, ? extends Either<? extends T, R>> leftFunction) { ... }
public <T> Either<L, T> flatMapRight(Function<? super R, ? extends Either<L, ? extends T>> rightFunction) { ... }
public <T> T converge(Function<? super L, ? extends T> leftFunction, Function<? super R, ? extends T> rightFunction) { ... }
public <T> T converge() { ... }
public static <T> T converge(Either<? extends T, ? extends T> either) { ... }
public void forEach(Consumer<? super L> leftAction, Consumer<? super R> rightAction) { ... }
}
查看上述代码实际运行的一种快速方法是查看 JUnit 测试的验证suite。