如何以惯用方式使用JUnit4来测试某些代码是否会抛出异常?
虽然我当然可以这样做:
@Test
public void testFooThrowsIndexOutOfBoundsException() {
boolean thrown = false;
try {
foo.doStuff();
} catch (IndexOutOfBoundsException e) {
thrown = true;
}
assertTrue(thrown);
}
我记得有一个注释或者一个Assert.xyz或者其他东西,对于这些类型的情况来说,远不如KUndgy和JUnit的精神 .
30 回答
JUnit 4支持这个:
参考:https://junit.org/junit4/faq.html#atests_7
你也可以这样做:
使用Java 8,您可以创建一个方法,将代码检查并将预期的异常作为参数:
然后在你的测试中:
优点:
不依赖任何库
本地化检查 - 更精确,并允许在一个测试中有多个断言,如果需要的话
易于使用
使用Java8的Junit4解决方案是使用此功能:
用法是:
请注意,唯一的限制是在lambda表达式中使用
final
对象引用 . 此解决方案允许继续测试断言,而不是期望使用@Test(expected = IndexOutOfBoundsException.class)
解决方案在方法级别上可提升 .现在JUnit 5已经发布,最好的选择是使用
Assertions.assertThrows()
(参见Junit 5 User Guide) .下面是一个验证抛出异常的示例,并使用Truth对异常消息进行断言:
其他答案中的方法优势是:
内置于JUnit
如果lambda中的代码没有抛出异常,你会得到一个有用的异常消息,如果它抛出一个不同的异常,你会得到一个栈跟踪
简洁
允许您的测试遵循Arrange-Act-Assert
您可以准确地指出您希望抛出异常的代码
您无需在
throws
子句中列出预期的异常您可以使用您选择的断言框架来对捕获的异常进行断言
类似的方法将添加到JUnit 4.13中的
org.junit Assert
.我建议库
assertj-core
在junit测试中处理异常在java 8中,像这样:
我使用Java 8 lambdas的解决方案:
您必须定义一个FunctionalInterface,因为
Runnable
没有声明所需的throws
.该方法可以如下使用:
Update: JUnit5对异常测试有一个改进:
assertThrows
.以下示例来自:Junit 5 User Guide
Original answer using JUnit 4.
有几种方法可以测试抛出异常 . 我在帖子中也讨论过以下选项How to write great unit tests with JUnit
设置
expected
参数@Test(expected = FileNotFoundException.class)
.使用
try
catch
使用
ExpectedException
规则进行测试 .您可以在JUnit4 wiki for Exception testing和bad.robot - Expecting Exceptions JUnit Rule中阅读有关异常测试的更多信息 .
在必须返回异常的方法之后,我们可以使用断言失败:
例如,您想为下面提到的代码片段编写Junit
上面的代码是测试可能发生的一些未知异常,下面的代码是使用自定义消息声明一些异常 .
为了解决同样的问题,我确实设置了一个小项目:http://code.google.com/p/catch-exception/
使用这个小助手你会写
这比JUnit 4.7的ExpectedException规则简洁得多 . 与skaffman提供的解决方案相比,您可以指定您期望异常的代码行 . 我希望这有帮助 .
JUnit 5解决方案
有关JUnit 5的更多信息,请访问http://junit.org/junit5/docs/current/user-guide/#writing-tests-assertions
编写测试用例有两种方法
使用方法抛出的异常对测试进行批注 . 像这样的东西
@Test(expected = IndexOutOfBoundsException.class)
您可以使用try catch块在测试类中捕获异常,并对从测试类中的方法抛出的消息进行断言 .
我希望这能回答你的问题快乐学习......
BDD样式解决方案:JUnit 4 Catch Exception AssertJ
源代码
依赖关系
怎么样:捕获一个非常一般的异常,确保它使它脱离catch块,然后断言异常的类是你期望的那样 . 如果a)异常是错误的类型(例如,如果你有一个Null指针)而b)没有抛出异常,那么这个断言将失败 .
如前所述,有许多方法可以处理JUnit中的异常 . 但是对于Java 8,还有另外一个:使用Lambda Expressions . 使用Lambda Expressions,我们可以实现如下语法:
assertThrown接受一个功能接口,其实例可以使用lambda表达式,方法引用或构造函数引用创建 . assertThrown接受该接口将期望并准备好处理异常 .
这是相对简单但功能强大的技术 .
看看这篇描述这种技术的博客文章:http://blog.codeleak.pl/2014/07/junit-testing-exception-with-java-8-and-lambda-expressions.html
源代码可以在这里找到:https://github.com/kolorobot/unit-testing-demo/tree/master/src/test/java/com/github/kolorobot/exceptions/java8
披露:我是博客和项目的作者 .
我在Mkyong blog找到的Junit 4最灵活,最优雅的答案 . 它具有使用
@Rule
注释的try/catch
的灵活性 . 我喜欢这种方法,因为您可以读取自定义异常的特定属性 .除了NamShubWriter所说的,请确保:
ExpectedException实例是 public (Related Question)
ExpectedException isn't 实例化为@Before方法 . 这个post清楚地解释了JUnit执行顺序的所有复杂性 .
做 not 这样做:
最后,this博客文章清楚地说明了如何断言抛出某个异常 .
在JUnit 4或更高版本中,您可以按如下方式测试异常
这提供了许多可用于改进JUnit测试的功能 .
如果您看到以下示例,我将在异常上测试3件事 .
抛出的异常类型
异常消息
异常的原因
小心使用预期的异常,因为它只声明 method 抛出了该异常,而不是测试中的 particular line of code .
我倾向于使用它来测试参数验证,因为这些方法通常非常简单,但更复杂的测试可能更适合:
申请判决 .
只需制作一个可以关闭和打开的匹配器,如下所示:
要使用它:
添加
public ExpectedException exception = ExpectedException.none();
,然后:Edit 现在JUnit5已经发布,最好的选择是使用
Assertions.assertThrows()
(参见my other answer) .如果您尚未迁移到JUnit 5,但可以使用JUnit 4.7,则可以使用ExpectedException规则:
这比
@Test(expected=IndexOutOfBoundsException.class)
要好得多,因为如果在foo.doStuff()
之前抛出IndexOutOfBoundsException
,测试将会失败有关详细信息,请参阅this article
tl;dr
pre-JDK8:我会推荐旧的
try
-catch
块 .post-JDK8:使用AssertJ或自定义lambdas来断言异常行为 .
无论是Junit 4还是JUnit 5 .
the long story
可以自己编写一个自己动手
try
-catch
块或使用JUnit工具(@Test(expected = ...)
或@Rule ExpectedException
JUnit规则功能) .但是这些方式并不那么优雅,并且不能很好地与其他工具混合使用 .
try
-catch
块您必须围绕测试行为编写块,并在catch块中写入断言,这可能没什么问题,但很多人发现这种样式会中断测试的读取流程 . 你还需要在try
块的末尾写一个Assert.fail
,否则测试可能会错过断言的一面; PMD,findbugs或Sonar将发现此类问题 .@Test(expected = ...)
功能很有趣,因为您可以编写更少的代码,然后编写此测试可能不太容易出现编码错误 . _75885这种做法缺乏一些领域 .如果测试需要检查异常上的其他内容,例如原因或消息(良好的异常消息非常重要,具有精确的异常类型可能还不够) .
此外,由于期望放在方法中,取决于测试代码的编写方式,然后测试代码的错误部分可能抛出异常,导致误报测试,我不确定PMD,findbugs或Sonar将会提供有关此类代码的提示 .
ExpectedException
规则也试图解决之前的警告,但使用期望风格时使用感觉有点尴尬,EasyMock用户非常清楚这种风格 . 对某些人来说可能很方便,但如果你遵循行为驱动开发(BDD)或安排行为断言(AAA)原则,ExpectedException
规则将不适合那些写作风格 . 除此之外,它可能会遇到与@Test
方式相同的问题,具体取决于您期望的位置 .即使预期的异常放在测试语句之前,如果测试遵循BDD或AAA,它也会破坏您的读取流程 .
另请参阅
ExpectedException
作者JUnit上的comment问题 .所以这些上面的选项都有他们的注意事项,显然不能免于编码器错误 .
正如项目描述所说的那样,它让编码器用流畅的代码行编写代码来捕获异常并为以后的断言提供此异常 . 您可以使用任何断言库,如Hamcrest或AssertJ .
从主页获取的一个快速示例:
正如您所看到的,代码非常简单,您可以在特定行上捕获异常,
then
API是将使用AssertJ API的别名(类似于使用assertThat(ex).hasNoCause()...
) . 在某些时候,该项目依赖于FEST-Assert的AssertJ的祖先 . EDIT: 该项目似乎正在酝酿Java 8 Lambdas支持 .目前这个库有两个缺点:
在撰写本文时,值得注意的是,该库基于Mockito 1.x,因为它创建了一个模拟场景背后的测试对象 . 由于Mockito仍未更新 this library cannot work with final classes or final methods . 即使它是基于当前版本的mockito 2,这也需要声明一个全局模拟制作者(
inline-mock-maker
),这可能不是你想要的东西,因为这个模拟器具有与常规模拟器不同的缺点 .它需要另一个测试依赖项 .
一旦库支持lambdas,这些问题将不适用,但AssertJ工具集将复制该功能 .
Taking all into account if you don't want to use the catch-exception tool, I will recommend the old good way of the try-catch block, at least up to the JDK7. And for JDK 8 users you might prefer to use AssertJ as it offers may more than just asserting exceptions.
并使用AssertJ进行示例测试:
正如您所注意到的,
assertEquals
仍在返回void
,因此不允许链接断言,如AssertJ .此外,如果你记得与
Matcher
或Assert
的名字冲突,请准备好与Assertions
相同的冲突 .我想得出结论,今天(2017-03-03) AssertJ 的易用性,可发现的API,快速的开发速度以及作为事实上的测试依赖是JDK8的最佳解决方案,无论测试框架如何(JUnit或之前的JDK应该依赖于 try-catch 块,即使它们感觉很笨拙 .
此答案已从another question复制,但没有相同的可见性,我是同一作者 .
JUnit内置了对此的支持,"expected" attribute
使用AssertJ断言,可以与JUnit一起使用:
它比
@Test(expected=IndexOutOfBoundsException.class)
更好,因为它保证了测试中的预期行引发异常,并让您检查有关异常的更多详细信息,例如消息,更容易:Maven/Gradle instructions here.
在我的情况下,我总是从db获得RuntimeException,但消息不同 . 并且需要分别处理异常 . 以下是我测试它的方式:
在junit中,有四种方法可以测试异常 .
所以
当您只想测试异常类型时使用第一种方式
当您想要进一步测试异常消息时,将使用其他三种方法
如果你使用junit 3,那么第三个是首选
如果你喜欢junit 5,那么你应该喜欢第4个
了解更多信息,您可以阅读this document和junit5 user guide了解详情 .
我在这里尝试了很多方法,但它们要么很复杂,要么不能满足我的要求 . 事实上,人们可以非常简单地编写一个辅助方法:
像这样用它:
零依赖:不需要mockito,不需要powermock;和最后的课程一起工作得很好 .
Java 8解决方案
如果您想要一个解决方案:
使用Java 8 lambda
不依赖于任何JUnit魔法
允许您在单个测试方法中检查多个异常
检查测试方法中特定行集引发的异常,而不是整个测试方法中的任何未知行
产生抛出的实际异常对象,以便您可以进一步检查它
这是我写的一个实用函数:
(taken from my blog)
使用方法如下:
恕我直言,在JUnit中检查异常的最佳方法是try / catch / fail / assert模式:
assertTrue
可能对某些人来说有点强,所以assertThat(e.getMessage(), containsString("the message");
可能更好 .