关于java:JUnit:另一种测试异常的模式

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 ExpectedException Rule怎么样?

首先,在测试类的顶部声明Rule

1
2
@Rule
public final ExpectedException ee = ExpectedException.none();

然后在您的测试方法中,您可以声明您可以期望Exception

1
2
3
4
5
@Test
public void testStuff() {
    ee.expect(IllegalArgumentException.class);
    ee.expectMessage("My Exception text");
}

我认为这比你的方法更清晰。

然后,您可以使用hamcrest Matcher来匹配Exception消息:

1
2
3
4
5
6
@Test
public void testStuff() {
    ee.expect(IllegalArgumentException.class);
    ee.expectMessage(containsString("error"));
    ee.expect(hasProperty("errorCode", is(7)));
}

hasProperty Matcher将查找命名属性的getter并检查它是否与第二个参数匹配 - 这是另一个Matcher

你甚至可以实现自己的Matcher,在这种情况下你不需要依赖hamcrest:

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));

使用static工厂方法和static导入,这可以变得非常干净:

1
ee.expect(exceptionWithErrorCode(7));

如果您有一个使用getErrorCode()方法定义业务Exception的公共interface,比如名为ErrorAwareException,那么您可以扩展TypeSafeMatcher< T >类以创建稍微更清晰的代码:

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,那么请确保在项目中包含junit-dep而不是pure junit,否则hamcrest类将与junit中包含的hamcrest类冲突。在maven中,这看起来像这样:

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>

我想你实际上是在这里重新发明轮子。您可以使用@Test注释的expected参数,这会导致测试方法成功抛出给定的异常。或者使用ExpectedException规则基本相同,但具有更多功能。所以试试吧

1
2
3
4
@Test(expected = Exception.class)
public void myTest() {
    throw new Exception();
}

要么

1
2
3
4
5
6
7
8
@Rule
private ExpectedException rule = ExpectedException.none();

@Test
public void myTest() {
    rule.expect(Exception.class);
    throw new Exception();
}