在JUnit中,有3种流行的方式来处理测试代码中的异常:
- 尝试捕捉习语
- 使用JUnit规则
- 带注解
尝试捕捉习语
这个习语是最受欢迎的习语之一,因为它已在JUnit 3中使用。
@Testpublic void throwsExceptionWhenNegativeNumbersAreGiven() {try {calculator.add("-1,-2,3");fail("Should throw an exception if one or more of given numbers are negative");} catch (Exception e) {assertThat(e).isInstanceOf(IllegalArgumentException.class).hasMessage("negatives not allowed: [-1, -2]");}}
上面的方法是一种常见的模式。 当没有引发异常并且在catch子句中验证了异常本身时,测试将失败(在上面的示例中,我使用了FEST Fluent断言),尽管它很好,但我更喜欢使用
ExpectedException规则。
使用JUnit规则
可以使用创建相同的示例
ExceptedException规则。 规则必须是标有@Rule批注的公共字段。 请注意,“抛出”规则可能会在许多测试中重复使用。
@Rulepublic ExpectedException thrown = ExpectedException.none();@Testpublic void throwsExceptionWhenNegativeNumbersAreGiven() {// arrangethrown.expect(IllegalArgumentException.class);thrown.expectMessage(equalTo("negatives not allowed: [-1, -2]"));// actcalculator.add("-1,-2,3");}
通常,我发现上面的代码更具可读性,因此我在项目中使用了这种方法。
当未引发异常时,您将收到以下消息: java.lang.AssertionError:预期引发的测试(java.lang.IllegalArgumentException的实例和带有消息“不允许负数的异常:[-1,-2]” ) 。 挺好的。
但并非所有例外情况我都可以通过上述方法进行检查。 有时我只需要检查抛出的异常的类型,然后使用@Test批注。
带注解
@Test (expected = IllegalArgumentException.class)public void throwsExceptionWhenNegativeNumbersAreGiven() {// actcalculator.add("-1,-2,3");}
当未引发异常时,您将收到以下消息: java.lang.AssertionError:预期的异常:java.lang.IllegalArgumentException
使用这种方法时,您需要小心。 有时很容易想到一般的Exception , RuntimeException甚至Throwable 。 这被认为是一种不好的做法,因为您的代码可能会在实际未预期的地方引发异常,并且测试仍将通过!
综上所述,在我的代码中,我使用两种方法: JUnit规则和注释 。 优点是:
- 代码不引发异常时的错误消息会自动处理
- 可读性得到改善
- 创建的代码更少
您的喜好是什么?
我听说过处理异常的第四种方式(我的一位同事在阅读本文后提出了建议)–使用自定义注释。
乍一看,实际上该解决方案看起来不错,但是它需要您自己的JUnit运行程序,因此它有缺点:您不能将此批注与Mockito运行程序一起使用。
作为编码实践,我创建了这样的注释,所以也许有人觉得它有用
用法
@RunWith(ExpectsExceptionRunner.class)
public class StringCalculatorTest {@Test@ExpectsException(type = IllegalArgumentException.class, message = "negatives not allowed: [-1]")public void throwsExceptionWhenNegativeNumbersAreGiven() throws Exception {// actcalculator.add("-1,-2,3");}}
上面的测试将失败,并显示一条消息: java.lang.Exception:意外的异常消息,预期的
但是是
注释
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface ExpectsException {Class type();String message() default "";
}
带有复制和粘贴代码的跑步者
public class ExpectsExceptionRunner extends BlockJUnit4ClassRunner {public ExpectsExceptionRunner(Class klass) throws InitializationError {super(klass);}@Overrideprotected Statement possiblyExpectingExceptions(FrameworkMethod method, Object test, Statement next) {ExpectsException annotation = method.getAnnotation(ExpectsException.class);if (annotation == null) {return next;}return new ExpectExceptionWithMessage(next, annotation.type(), annotation.message());}class ExpectExceptionWithMessage extends Statement {private final Statement next;private final Class expected;private final String expectedMessage;public ExpectExceptionWithMessage(Statement next, Class expected, String expectedMessage) {this.next = next;this.expected = expected;this.expectedMessage = expectedMessage;}@Overridepublic void evaluate() throws Exception {boolean complete = false;try {next.evaluate();complete = true;} catch (AssumptionViolatedException e) {throw e;} catch (Throwable e) {if (!expected.isAssignableFrom(e.getClass())) {String message = "Unexpected exception, expected<"+ expected.getName() + "> but was <"+ e.getClass().getName() + ">";throw new Exception(message, e);}if (isNotNull(expectedMessage) && !expectedMessage.equals(e.getMessage())) {String message = "Unexpected exception message, expected<"+ expectedMessage + "> but was<"+ e.getMessage() + ">";throw new Exception(message, e);}}if (complete) {throw new AssertionError("Expected exception: "+ expected.getName());}}private boolean isNotNull(String s) {return s != null && !s.isEmpty();}}}
翻译自: https://www.javacodegeeks.com/2013/11/3-ways-of-handling-exceptions-in-junit-which-one-to-choose.html