在Junit中处理异常的方式有很多种,比如:
3 ways of handling exceptions in JUnit. Which one to choose?
JUnit ExpectedException rule: beyond basics
有人推荐我尝试一下catch-exception,在这篇文章中我将会向大家介绍。简而言之,catch-exception库可以仅在一行代码中捕获异常,以后再对其进行处理。
通过Maven安装
为了快速进入正题,我使用了一个包含有一系列依赖关系JUnit, Mocito, Hamcrest, AssertJ的Unit Testing Demo项目作为示例,并添加了catch-exception:
1 2 3 4 5 6 |
<dependency> <groupId>com.googlecode.catch-exception</groupId> <artifactId>catch-exception</artifactId> <version>1.2.0</version> <scope>test</scope> </dependency> |
依存关系树如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
[INFO] --- maven-dependency-plugin:2.1:tree @ unit-testing-demo --- [INFO] com.github.kolorobot:unit-testing-demo:jar:1.0.0-SNAPSHOT [INFO] +- org.slf4j:slf4j-api:jar:1.5.10:compile [INFO] +- org.slf4j:jcl-over-slf4j:jar:1.5.10:runtime [INFO] +- org.slf4j:slf4j-log4j12:jar:1.5.10:runtime [INFO] +- log4j:log4j:jar:1.2.15:runtime [INFO] +- junit:junit:jar:4.11:test [INFO] +- org.mockito:mockito-core:jar:1.9.5:test [INFO] +- org.assertj:assertj-core:jar:1.5.0:test [INFO] +- org.hamcrest:hamcrest-core:jar:1.3:test [INFO] +- org.hamcrest:hamcrest-library:jar:1.3:test [INFO] +- org.objenesis:objenesis:jar:1.3:test [INFO] - com.googlecode.catch-exception:catch-exception:jar:1.2.0:test |
准备开始
被测系统(SUT):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
class ExceptionThrower { void someMethod() { throw new RuntimeException("Runtime exception occurred"); } void someOtherMethod() { throw new RuntimeException("Runtime exception occurred", new IllegalStateException("Illegal state")); } void yetAnotherMethod(int code) { throw new CustomException(code); } } |
使用AssertJ
断言的BDD-style方法catch-exception示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
import org.junit.Test; import static com.googlecode.catchexception.CatchException.*; import static com.googlecode.catchexception.apis.CatchExceptionAssertJ.*; public class CatchExceptionsTest { @Test public void verifiesTypeAndMessage() { when(new SomeClass()).someMethod(); then(caughtException()) .isInstanceOf(RuntimeException.class) .hasMessage("Runtime exception occurred") .hasMessageStartingWith("Runtime") .hasMessageEndingWith("occured") .hasMessageContaining("exception") .hasNoCause(); } } |
看起来不错——简单,可读性高。没有JUnit运行。请注意,我指定的那个在SomeClass
类中抛出异常的方法。可想而知,我可以在一个测试中检验多个异常。但是这违背了测试中的单一任务原则,所以不推荐这种做法。另外,如果你是用Eclipse工作的话,也许这篇文章对你有用。
检查异常的原因
我相信下面的代码就没有必要讨论了吧:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
import org.junit.Test; import static com.googlecode.catchexception.CatchException.*; import static com.googlecode.catchexception.apis.CatchExceptionAssertJ.*; public class CatchExceptionsTest { @Test public void verifiesCauseType() { when(new ExceptionThrower()).someOtherMethod(); then(caughtException()) .isInstanceOf(RuntimeException.class) .hasMessage("Runtime exception occurred") .hasCauseExactlyInstanceOf(IllegalStateException.class) .hasRootCauseExactlyInstanceOf(IllegalStateException.class); } } |
使用Hamcrest来检查自定义异常
为了检查自定义异常,我用了在之前的文章中谈到的Hamcrest匹配代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
class CustomException extends RuntimeException { private final int code; public CustomException(int code) { this.code = code; } public int getCode() { return code; } } class ExceptionCodeMatches extends TypeSafeMatcher<CustomException> { private int expectedCode; public ExceptionCodeMatches(int expectedCode) { this.expectedCode = expectedCode; } @Override protected boolean matchesSafely(CustomException item) { return item.getCode() == expectedCode; } @Override public void describeTo(Description description) { description.appendText("expects code ") .appendValue(expectedCode); } @Override protected void describeMismatchSafely(CustomException item, Description mismatchDescription) { mismatchDescription.appendText("was ") .appendValue(item.getCode()); } } |
测试部分:
1 2 3 4 5 6 7 8 9 10 11 |
import org.junit.Test; import static com.googlecode.catchexception.CatchException.*; import static org.junit.Assert.*; public class CatchExceptionsTest { @Test public void verifiesCustomException() { catchException(new ExceptionThrower(), CustomException.class).yetAnotherMethod(500); assertThat((CustomException) caughtException(), new ExceptionCodeMatcher(500)); |
总结
catch-exception看起来很棒,上手非常简单。比起在JUnit中的方法它具有很多优势。如果有机会我竟会深入的研究一下这个库,希望在现实中能有一个这样的机会。
- 文章中的代码可以在这里找到: Unit Testing Demo
如果你感兴趣,可以看一下我其他的文章:
- JUnit中处理异常的3种方式,选择哪一种?
- JUnit中的ExceptedException规则:基础之外的
- 怎样在一个Maven项目(JUnit, Mocito, Hamcrest, AssertJ)中测试依赖关系
- 用静态成员为变量类型改善辅助内容
英文原文由Rafal Borowiec发表在javacodegeeks,赖信涛翻译,Importnew校对。