【问题标题】:JUnit: another pattern to test exceptionJUnit:另一种测试异常的模式
【发布时间】:2013-10-02 20:58:23
【问题描述】:

我在一个包含许多嵌入 errorCode 的“BusinessException”的项目中工作。

在每个异常单元测试中,我都必须测试这些重复这种模式的错误代码:

@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 类。我使用一个抽象类来处理异常断言测试。

公共抽象类 TestExceptionUtil { 公共无效runAndExpectException(类expectedException,字符串expectedErrorCode){ String failUnexpectedExceptionMessage = "意外异常。预期为:'%s',但得到:'%s'"; 尝试 { 代码执行(); fail("'" + expectedException.getName() + "' 没有抛出"); } 捕捉(业务异常 e){ if (e.getClass().equals(expectedException)) { assertThat("不应出现异常错误代码", e.getErrorCode(), is(expectedErrorCode)); } 别的 { 失败(String.format(failUnexpectedExceptionMessage,expectedException.getName(),e)); } } 捕捉(异常 e){ 失败(String.format(failUnexpectedExceptionMessage,expectedException.getName(),e)); } } 抽象公共无效codeToExecute(); }

那么,客户端就是这样使用的:

@Test
public void zipFileReaderCtorShouldThrowAnExceptionWithInexistingArchive() {
    new TestExceptionUtil() {
        @Override
        public void codeToExecute() {
            zfr = new ZipFileReader("unexpected/path/to/file");
        }
    }.runAndExpectException(BusinessTechnicalException.class, ErrorCode.FILE_NOT_FOUND);
}

你觉得它“干净”吗?你觉得可以改善吗?你认为它太重和/或无用吗? 我的主要目标是在我们的开发团队中统一测试异常。 (当然还有分解代码)

感谢阅读!

【问题讨论】:

    标签: java unit-testing design-patterns exception-handling junit


    【解决方案1】:

    JUnit ExpectedException Rule 怎么样?

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

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

    然后在你的测试方法中你可以声明你可以期待一个Exception:

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

    我认为这比你的方法干净得多。

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

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

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

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

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

    这将按如下方式使用:

    ee.expect(new ErrorCodeMatcher(7));
    

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

    ee.expect(exceptionWithErrorCode(7));
    

    如果您有一个通用的interface,它使用getErrorCode() 方法定义您的业务Exception,例如称为ErrorAwareException,那么您可以扩展TypeSafeMatcher&lt;T&gt; 类以创建更简洁的代码:

    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 而不是纯 junit,否则 hamcrest 类将与 junit 中包含的 hamcrest 类发生冲突。在 Maven 中,这看起来像这样:

    <dependency>
        <groupId>org.hamcrest</groupId>
        <artifactId>hamcrest-all</artifactId>
        <version>1.3</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit-dep</artifactId>
        <version>4.11</version>
        <scope>test</scope> 
    </dependency>
    

    【讨论】:

      【解决方案2】:

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

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

      @Rule
      private ExpectedException rule = ExpectedException.none();
      
      @Test
      public void myTest() {
          rule.expect(Exception.class);
          throw new Exception();
      }
      

      【讨论】:

      猜你喜欢
      • 2012-04-25
      • 2011-01-28
      • 1970-01-01
      • 2013-02-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-12-22
      • 2020-05-13
      相关资源
      最近更新 更多