如何以惯用方式使用 JUnit4 来测试某些代码是否引发异常?
虽然我当然可以做这样的事情:
@Test public void testFooThrowsIndexOutOfBoundsException() { boolean thrown = false; try { foo.doStuff(); } catch (IndexOutOfBoundsException e) { thrown = true; } assertTrue(thrown); }
我记得有一个注解或一个 Assert.xyz 或一些在这种情况下不那么笨拙和更符合 JUnit 精神的东西。
编辑:现在 JUnit 5 和 JUnit 4.13 已经发布,最好的选择是使用Assertions.assertThrows() (对于 JUnit 5)和Assert.assertThrows()(对于 JUnit 4.13+)。
Assertions.assertThrows()
Assert.assertThrows()
如果您还没有迁移到 JUnit 5,但可以使用 JUnit 4.7,则可以使用ExpectedExceptionRule:
ExpectedException
public class FooTest { @Rule public final ExpectedException exception = ExpectedException.none(); @Test public void doStuffThrowsIndexOutOfBoundsException() { Foo foo = new Foo(); exception.expect(IndexOutOfBoundsException.class); foo.doStuff(); } }
这比因为如果之前抛出@Test(expected=IndexOutOfBoundsException.class)测试将失败要好得多IndexOutOfBoundsException``foo.doStuff()
@Test(expected=IndexOutOfBoundsException.class)
IndexOutOfBoundsException``foo.doStuff()
try
catch
fail()
不管是 Junit 4 还是 JUnit 5。
长篇大论
可以自己编写一个自己动手 try-catch阻止或使用 JUnit 工具(@Test(expected = ...)或@Rule ExpectedExceptionJUnit 规则功能)。
@Test(expected = ...)
@Rule ExpectedException
但是这些方法并不那么优雅,并且不能很好地将可读性与其他工具混合。此外,JUnit 工具确实存在一些缺陷。
-块您必须围绕测试try的catch行为编写块并在 catch 块中写入断言,这可能很好,但许多人发现这种样式会中断测试的读取流程。此外,您需要Assert.fail在块的末尾写一个try。否则,测试可能会错过断言的一侧;PMD、findbugs或Sonar会发现此类问题。
Assert.fail
该@Test(expected = ...)功能很有趣,因为您可以编写更少的代码,然后编写此测试据说不太容易出现编码错误。但这种方法在某些领域是缺乏的。
如果测试需要检查异常的其他内容,例如原因或消息(好的异常消息非常重要,拥有精确的异常类型可能还不够)。
此外,由于期望被放在方法中,根据测试代码的编写方式,测试代码的错误部分可能会引发异常,导致误报测试,我不确定PMD、findbugs或Sonar将提供有关此类代码的提示。
@Test(expected = WantedException.class) public void call2_should_throw_a_WantedException__not_call1() { // init tested tested.call1(); // may throw a WantedException // call to be actually tested tested.call2(); // the call that is supposed to raise an exception }
@Test
@Rule ExpectedException thrown = ExpectedException.none() @Test public void call2_should_throw_a_WantedException__not_call1() { // expectations thrown.expect(WantedException.class); thrown.expectMessage("boom"); // init tested tested.call1(); // may throw a WantedException // call to be actually tested tested.call2(); // the call that is supposed to raise an exception }
即使预期的异常放在测试语句之前,如果测试遵循 BDD 或 AAA,它也会破坏您的阅读流程。
另外,请参阅ExpectedException. JUnit 4.13-beta-2甚至弃用了这种机制:
拉取请求 #1519:弃用 ExpectedException Assert.assertThrows 方法提供了一种更好的方法来验证异常。此外,ExpectedException 的使用在与 TestWatcher 等其他规则一起使用时容易出错,因为在这种情况下规则的顺序很重要。
拉取请求 #1519:弃用 ExpectedException
Assert.assertThrows 方法提供了一种更好的方法来验证异常。此外,ExpectedException 的使用在与 TestWatcher 等其他规则一起使用时容易出错,因为在这种情况下规则的顺序很重要。
因此,上述这些选项有很多警告,显然不能避免编码错误。
正如该项目的描述所说,它让编码人员用流畅的代码行编写捕获异常并为后一个断言提供此异常。您可以使用任何断言库,例如Hamcrest或AssertJ。
取自主页的快速示例:
// given: an empty list List myList = new ArrayList(); // when: we try to get the first element of the list when(myList).get(1); // then: we expect an IndexOutOfBoundsException then(caughtException()) .isInstanceOf(IndexOutOfBoundsException.class) .hasMessage("Index: 1, Size: 0") .hasNoCause();
如您所见,代码非常简单,您在特定行捕获异常,thenAPI 是一个别名,将使用 AssertJ API(类似于 using assertThat(ex).hasNoCause()...)。在某些时候,该项目依赖于 AssertJ 的祖先 FEST-Assert。编辑:似乎该项目正在酝酿对 Java 8 Lambdas 的支持。
then
assertThat(ex).hasNoCause()...
目前,这个库有两个缺点:
inline-mock-maker
一旦库支持 lambda,这些问题将不适用。但是,AssertJ 工具集将复制该功能。
综合考虑如果不想用catch-exception工具的话,我会推荐-block的老好办法try,catch至少到JDK7。对于 JDK 8 用户,您可能更喜欢使用 AssertJ,因为它提供的可能不仅仅是断言异常。
以及使用AssertJ的示例测试:
@Test public void test_exception_approach_1() { ... assertThatExceptionOfType(IOException.class) .isThrownBy(() -> someBadIOOperation()) .withMessage("boom!"); } @Test public void test_exception_approach_2() { ... assertThatThrownBy(() -> someBadIOOperation()) .isInstanceOf(Exception.class) .hasMessageContaining("boom"); } @Test public void test_exception_approach_3() { ... // when Throwable thrown = catchThrowable(() -> someBadIOOperation()); // then assertThat(thrown).isInstanceOf(Exception.class) .hasMessageContaining("boom"); }
assertThrows
@Test @DisplayName("throws EmptyStackException when peeked") void throwsExceptionWhenPeeked() { Throwable t = assertThrows(EmptyStackException.class, () -> stack.peek()); Assertions.assertEquals("...", t.getMessage()); }
正如您所注意到的,assertEquals它仍在返回void,因此不允许像 AssertJ 那样链接断言。
assertEquals
void
此外,如果您记得名字与Matcheror发生冲突Assert,请准备好与 . 发生相同的冲突Assertions。
Matcher
Assert
Assertions
我想得出结论,今天 (2017-03-03) AssertJ的易用性、可发现的 API、快速的开发速度以及作为事实上的测试依赖项是 JDK8 的最佳解决方案,无论测试框架如何(JUnit与否),以前的 JDK 应该改为依赖try-catch块,即使它们感觉很笨重。