JUnit: another pattern to test exception
我在一个项目中工作,其中包含许多嵌入了errorCode的"BusinessException"。
在每个单元测试异常中,我必须测试这些错误代码重复这种模式:
1 2 3 4 5 6 7 8 9 10 11 | @Test public void zipFileReaderCtorShouldThrowAnExceptionWithInexistingArchive() { try { zfr = new ZipFileReader("unexpected/path/to/file"); fail("'BusinessZipException' not throwed"); } catch (BusinessZipException e) { assertThat("Unexpected error code", e.getErrorCode(), is(ErrorCode.FILE_NOT_FOUND)); } catch (Exception e) { fail("Unexpected Exception: '" + e +"', expected: 'BusinessZipException'"); } } |
(由于错误代码测试,使用JUnit注释是不可能的)
我很无聊,特别是因为我必须在fail()的错误消息中复制/粘贴异常名称。
所以,我写了一个Util类。 我使用抽象类来处理异常断言测试。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | public abstract class TestExceptionUtil { public void runAndExpectException(Class expectedException, String expectedErrorCode) { String failUnexpectedExceptionMessage ="Unexpected exception. Expected is: '%s', but got: '%s'"; try { codeToExecute(); fail("'" + expectedException.getName() +"' not throwed"); } catch (BusinessException e) { if (e.getClass().equals(expectedException)) { assertThat("Exception error code not expected", e.getErrorCode(), is(expectedErrorCode)); } else { fail(String.format(failUnexpectedExceptionMessage, expectedException.getName(), e)); } } catch (Exception e) { fail(String.format(failUnexpectedExceptionMessage, expectedException.getName(), e)); } } abstract public void codeToExecute(); } |
然后,客户端以这种方式使用它:
1 2 3 4 5 6 7 8 9 | @Test public void zipFileReaderCtorShouldThrowAnExceptionWithInexistingArchive() { new TestExceptionUtil() { @Override public void codeToExecute() { zfr = new ZipFileReader("unexpected/path/to/file"); } }.runAndExpectException(BusinessTechnicalException.class, ErrorCode.FILE_NOT_FOUND); } |
你觉得它"干净"吗? 你认为它可以改善吗? 你认为它太沉重和/或没用吗?
我的主要目标是在开发团队中统一测试异常。 (当然还有代码化代码)
谢谢阅读!
JUnit
首先,在测试类的顶部声明
1 2 | @Rule public final ExpectedException ee = ExpectedException.none(); |
然后在您的测试方法中,您可以声明您可以期望
1 2 3 4 5 | @Test public void testStuff() { ee.expect(IllegalArgumentException.class); ee.expectMessage("My Exception text"); } |
我认为这比你的方法更清晰。
然后,您可以使用hamcrest
1 2 3 4 5 6 | @Test public void testStuff() { ee.expect(IllegalArgumentException.class); ee.expectMessage(containsString("error")); ee.expect(hasProperty("errorCode", is(7))); } |
你甚至可以实现自己的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | public class ErrorCodeMatcher extends BaseMatcher<Throwable> { private final int expectedErrorCode; public ErrorCodeMatcher(int expectedErrorCode) { this.expectedErrorCode = expectedErrorCode; } @Override public boolean matches(Object o) { return ((BusinessZipException) o).getErrorCode() == expectedErrorCode; } @Override public void describeTo(Description d) { d.appendText("Expected error code was" + expectedErrorCode); } } |
这将使用如下:
1 | ee.expect(new ErrorCodeMatcher(7)); |
使用
1 | ee.expect(exceptionWithErrorCode(7)); |
如果您有一个使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | public class ErrorCodeMatcher<T extends Exception & ErrorAwareException> extends TypeSafeMatcher< T > { public static <E extends Exception & ErrorAwareException> ErrorCodeMatcher<E> exceptionWithErrorCode(final int expectedErrorCode) { return new ErrorCodeMatcher<E>(expectedErrorCode); } private final int expectedErrorCode; public ErrorCodeMatcher(int expectedErrorCode) { this.expectedErrorCode = expectedErrorCode; } @Override protected boolean matchesSafely(final T t) { return t.getErrorCode() == expectedErrorCode; } @Override public void describeTo(Description d) { d.appendText("Expected error code was" + expectedErrorCode); } } |
请注意,如果您确实选择使用hamcrest,那么请确保在项目中包含
1 2 3 4 5 6 7 8 9 10 11 12 | <dependency> <groupId>org.hamcrest</groupId> hamcrest-all</artifactId> <version>1.3</version> <scope>test</scope> </dependency> <dependency> <groupId>junit</groupId> junit-dep</artifactId> <version>4.11</version> <scope>test</scope> </dependency> |
我想你实际上是在这里重新发明轮子。您可以使用
要么
1 2 3 4 5 6 7 8 |