How to test that no exception is thrown?
我知道这样做的一个方法是:
1 2 3 4 5 6 7 8 9 | @Test public void foo(){ try{ //execute code that you expect not to throw Exceptions. } catch(Exception e){ fail("Should not have thrown any exception"); } } |
有没有更干净的方法来做这个?(可能是用Junit的
你走错了方向。只需测试您的功能:如果抛出异常,测试将自动失败。如果没有异常,您的测试将全部变为绿色。
我已经注意到这个问题不时引起人们的兴趣,所以我会扩大一点。
单元测试背景当你进行单元测试时,重要的是给自己定义你认为的工作单元。基本上:代码库的提取,可能包括也可能不包括表示单个功能的多个方法或类。
或者,如Roy Osherove第二版《单元测试技术》第11页所述:
A unit test is an automated piece of code that invokes the unit of work being tested, and then checks some assumptions about a single end result of that unit. A unit test is almost always written using a unit testing framework. It can be written easily and runs quickly. It's trustworthy, readable, and maintainable. It's consistent in its results as long as production code hasn't changed.
重要的是要认识到,一个工作单元通常不仅仅是一种方法,而且在非常基础的层面上,它是一种方法,然后它被其他工作单元封装。
理想情况下,您应该为每个单独的工作单元都有一个测试方法,这样您就可以随时查看哪里出了问题。在本例中,有一个名为
第一个工作单元应该测试在输入有效和无效的情况下是否返回有效用户。数据源引发的任何异常都必须在此处处理:如果不存在用户,则应该有一个测试来证明在找不到用户时引发了异常。其中的一个示例可以是
一旦您处理了这个基本工作单元的所有用例,就可以提升一个级别。在这里,您做的完全相同,但是您只处理来自当前级别正下方级别的异常。这使您的测试代码保持了良好的结构,并允许您快速地运行整个体系结构,以发现哪里出了问题,而不是必须到处跳。
处理测试的有效和错误输入现在应该清楚我们将如何处理这些异常。输入有两种类型:有效输入和错误输入(输入严格意义上是有效的,但不正确)。
当您使用有效输入工作时,您将设置隐式期望值,您编写的任何测试都将工作。
这样的方法调用可以如下所示:
通过添加另一个使用错误输入并期望出现异常的测试(
您试图在测试中做两件事:检查有效输入和错误输入。通过将其分为两种方法,每种方法只做一件事,您将有更清晰的测试和对哪里出错的更好的概述。
通过记住分层的工作单元,您还可以减少层次结构中较高的层所需的测试量,因为您不必考虑较低层中可能发生错误的每件事情:当前层以下的层是您的依赖性工作的虚拟保证,如果出现问题,它在您当前的层中(假设较低层本身不会抛出任何错误)。
我偶然发现这是因为Sonarkube规则"Squid:s2699":"在这个测试用例中至少添加一个断言"。
我有一个简单的测试,它的唯一目标是通过测试而不抛出异常。
考虑这个简单的代码:
1 2 3 4 5 6 |
可以添加哪种断言来测试此方法?当然,您可以试着绕过它,但这只是代码膨胀。
解决方案来自JUnit本身。
如果没有异常,并且您希望显式地说明这种行为,只需添加
1 2 3 4 | @Test(expected = Test.None.class /* no exception expected */) public void test_printLine() { Printer.printLine("line"); } |
Java 8使这更容易,KOTLIN / Scala加倍。
我们可以写一个实用程序类
1 2 3 4 5 6 7 8 9 10 11 12 |
然后你的代码变得简单:
1 2 3 4 5 6 | @Test public void foo(){ MyAssertions.assertDoesNotThrow(() -> { //execute code that you expect not to throw Exceptions. } } |
如果您不能访问Java8,我将使用一个痛苦的旧Java工具:Adple代码块和一个简单的注释。
1 2 3 4 5 6 7 8 9 10 |
最后,我爱上了Kotlin这门语言:
1 2 3 4 5 6 7 8 9 |
尽管有很多空间来摆弄你到底想表达什么,但我一直是一个流利断言的粉丝。
关于
You're approaching this the wrong way. Just test your functionality: if an exception is thrown the test will automatically fail. If no exception is thrown, your tests will all turn up green.
这在原则上是正确的,但在结论上是错误的。
Java允许控制流的异常。这是由jre运行时本身在类似于
假设您已经编写了一个组件来验证
1 2 3 4 5 6 7 8 9 10 11 | @Test public void given_validator_accepts_string_result_should_be_interpretable_by_doubleParseDouble(){ //setup String input ="12.34E+26" //a string double with domain significance //act boolean isValid = component.validate(input) //assert -- using the library 'assertJ', my personal favourite assertThat(isValid).describedAs(input +" was considered valid by component").isTrue(); assertDoesNotThrow(() -> Double.parseDouble(input)); } |
我还建议您使用
我特别不同意的是使用
如果您不走运,无法捕获代码中的所有错误。你可以愚蠢地做
1 2 3 4 5 6 7 8 9 10 11 12 |
使用Assertj Fluent断言3.7.0:
1 2 | Assertions.assertThatCode(() -> toTest.method()) .doesNotThrowAnyException(); |
Junit 5(Jupiter)提供三个功能来检查异常是否存在:
● 断言所有提供的
&声明执行 提供
 此功能可用 自6月5.2.0日(2018年4月29日)起。
● 断言执行所提供的
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 | package test.mycompany.myapp.mymodule; import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Test; class MyClassTest { @Test void when_string_has_been_constructed_then_myFunction_does_not_throw() { String myString ="this string has been constructed"; assertAll(() -> MyClass.myFunction(myString)); } @Test void when_string_has_been_constructed_then_myFunction_does_not_throw__junit_v520() { String myString ="this string has been constructed"; assertDoesNotThrow(() -> MyClass.myFunction(myString)); } @Test void when_string_is_null_then_myFunction_throws_IllegalArgumentException() { String myString = null; assertThrows( IllegalArgumentException.class, () -> MyClass.myFunction(myString)); } } |
JUnit5为此目的添加了assertAll()方法。
1 | assertAll( () -> foo() ) |
来源:JUnit 5 API
您可以使用@rule,然后调用方法reportMissingExceptionWithMessage,如下所示:这是斯卡拉,但它可以很容易地在Java中类似地完成。
可以预料,创建规则不会引发异常。
1 2 | @Rule public ExpectedException expectedException = ExpectedException.none(); |
使用断言空(…)
1 2 3 4 5 6 7 8 | @Test public void foo() { try { //execute code that you expect not to throw Exceptions. } catch (Exception e){ assertNull(e); } } |
如果要测试测试目标是否使用异常。只需将测试保留为(使用jmock2模拟合作者):
1 2 3 4 5 6 7 8 9 10 11 12 | @Test public void consumesAndLogsExceptions() throws Exception { context.checking(new Expectations() { { oneOf(collaborator).doSth(); will(throwException(new NullPointerException())); } }); target.doSth(); } |
如果目标确实使用抛出的异常,则测试将通过,否则测试将失败。
如果您想测试异常消费逻辑,事情会变得更加复杂。我建议把消费委托给一个可以被嘲笑的合作者。因此,测试可以是:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | @Test public void consumesAndLogsExceptions() throws Exception { Exception e = new NullPointerException(); context.checking(new Expectations() { { allowing(collaborator).doSth(); will(throwException(e)); oneOf(consumer).consume(e); } }); target.doSth(); } |
但如果你只是想记录它,有时它是过度设计的。在这种情况下,如果您坚持使用TDD,本文(http://java.dzone.com/articles/monitoring-declarative-transac,http://blog.novoj.net/2008/09/20/testing-aspect-pointcuts-is-there-an-easy-way/)可能会有所帮助。
这可能不是最好的方法,但它可以确保不会从正在测试的代码块中抛出异常。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | import org.assertj.core.api.Assertions; import org.junit.Test; public class AssertionExample { @Test public void testNoException(){ assertNoException(); } private void assertException(){ Assertions.assertThatThrownBy(this::doNotThrowException).isInstanceOf(Exception.class); } private void assertNoException(){ Assertions.assertThatThrownBy(() -> assertException()).isInstanceOf(AssertionError.class); } private void doNotThrowException(){ //This method will never throw exception } } |
以下各项未通过所有异常的测试,选中或未选中:
1 2 3 4 5 6 7 8 9 |