如何testing没有抛出exception?

我知道有一种方法可以做到:

@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的@Rule ?)

你正在接近这个错误的方式。 只要testing你的function:如果抛出exception,testing会自动失败。 如果没有例外,你的testing将全部变成绿色。

我已经注意到这个问题不时得到关注,所以我会扩大一点。

unit testing的背景

在进行unit testing时,要自己定义你认为的一个工作单元是很重要的。 基本上:提取你的代码库,可能包括也可能不包括代表单个function的多个方法或类。

或者,如Roy Osherove的第二版unit testing的艺术中所定义的,第11页:

unit testing是自动的一段代码,调用被testing的工作单元,然后检查一些关于该单元最终结果的假设。 unit testing几乎总是使用unit testing框架来编写的。 它可以很容易地编写和运行。 它值得信赖,可读和可维护。 只要生产代码没有改变,它的结果是一致的。

重要的是要认识到,一个单位的工作通常不只是一个方法,而是在一个基本的层面上是一个方法,之后是其他单位的工作。

在这里输入图像描述

理想情况下,你应该为每一个单独的工作单位都有一个testing方法,这样你就可以随时查看出错的地方。 在这个例子中,有一个叫做getUserById()的基本方法,它将返回一个用户,总共有3个工作单元。

第一个工作单元应该testing在有效和无效input的情况下是否返回有效的用户。
数据源引发的任何exception都必须在这里处理:如果没有用户存在,那么应该有一个testing,表明当找不到用户时抛出exception。 这个示例可能是IllegalArgumentException ,它被@Test(expected = IllegalArgumentException.class)注解捕获。

一旦你处理了这个基本工作单元的所有用例,你就向上移动。 在这里你完全一样,但是你只处理来自当前下方的exception。 这样可以使testing代码保持良好的结构,并使您能够快速浏览体系结构,以查找出错的地方,而不必在整个地方跳转。

处理testing的有效和错误的input

在这一点上,应该清楚我们将如何处理这些例外情况。 有两种types的input: 有效input和错误input(严格意义上input有效,但不正确)。

当你使用有效的input时,你可以设置隐含的期望值,无论你写什么testing,都可以工作。

这样的方法调用可以看起来像这样: existingUserById_ShouldReturn_UserObject 。 如果这个方法失败(例如抛出一个exception),那么你知道出了什么问题,你可以开始挖掘。

通过添加使用错误input的另一个testing( nonExistingUserById_ShouldThrow_IllegalArgumentException ),并期望发生exception,您可以看到您的方法是否执行错误的input。

TL; DR

您正在尝试在您的testing中做两件事:检查有效和错误的input。 把它分成两个方法,每个方法做一件事情,你会有更清晰的testing,并更好地概述事情出错的地方。

通过记住分层工作单元,还可以减less层次结构中层次较高的图层所需的testing数量,因为您不必考虑较低层中可能出错的所有东西:当前层下面的层是一个虚拟的保证,你的依赖性工作,如果出现错误,它在你当前的层(假设下层不会自己抛出任何错误)。

Java 8使这更容易,而Kotlin / Scala更是如此。

我们可以写一点实用课程

 class MyAssertions{ public static void assertDoesNotThrow(FailingRunnable action){ try{ action.run() } catch(Exception ex){ throw new Error("expected action not to throw, but it did!", ex) } } } @FunctionalInterface interface FailingRunnable { void run() throws Exception } 

然后你的代码变得简单:

 @Test public void foo(){ MyAssertions.assertDoesNotThrow(() -> { //execute code that you expect not to throw Exceptions. } } 

如果你没有访问Java-8,我会使用一个古老的Java工具:任意代码块和一个简单的评论

 //setup Component component = new Component(); //act configure(component); //assert /*assert does not throw*/{ component.doSomething(); } 

最后,我用最近爱上的语言kotlin:

 fun (() -> Any?).shouldNotThrow() = try { invoke() } catch (ex : Exception){ throw Error("expected not to throw!", ex) } @Test fun `when foo happens should not throw`(){ //... { /*code that shouldn't throw*/ }.shouldNotThrow() } 

虽然有很多的空间来摆脱你想expression这一点,我一直是stream利的断言粉丝。


关于

你正在接近这个错误的方式。 只要testing你的function:如果抛出exception,testing会自动失败。 如果没有例外,你的testing将全部变成绿色。

原则上是正确的,但结论不正确。

Java允许控制stream的exception。 这是由JRE运行时本身在API中完成的,如Double.parseDouble通过NumberFormatExceptionPaths.get通过InvalidPathException

鉴于你已经写了一个validationDouble.ParseDouble数字string的Double.ParseDouble ,也许使用一个正则expression式,也许是一个手写的parsing器,或者是embedded一些其他域的规则,限制双重的范围到某些特定的东西,如何最好的testing这个组件? 我认为一个明显的testing是断言,当分析结果string时,不会引发exception。 我会使用上面的assertDoesNotThrow/*comment*/{code}块来编写testing。 就像是

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

我也鼓励你使用TheoriesParameterized input这个testing,这样你可以更容易的重复使用这个testing来进行其他的input。 另外,如果你想去异国情调,你可以去一个testing生成工具 (和这个 )。 TestNG对参数化testing有更好的支持。

我觉得特别不愉快的是使用@Test(expectedException=IllegalArgumentException.class)这个例外是危险的广泛 。 如果你的代码改变,使得被测组件的构造函数if(constructorArgument <= 0) throw IllegalArgumentException() ,并且你的testing为这个参数提供了0,因为它很方便 – 这是很常见的,因为好的生成testing数据是一个令人惊讶的难题 – 然后,你的testing将是绿条,即使它什么都不testing。 这样的testing比无用的更糟糕。

如果你运气不好,赶上代码中的所有错误。 你可以愚蠢地做

 class DumpTest { Exception ex; @Test public void testWhatEver() { try { thisShouldThroughError(); } catch (Exception e) { ex = e; } assertEquals(null,ex); } } 

由于SonarQubes规则“squid:S2699”,我偶然发现了这个:“在这个testing用例中添加至less一个断言。

我做了一个简单的testing,那是唯一的目标。

想象一下这个简单的代码:

 public class Printer { public static void printLine(final String line) { System.out.println(line); } } 

可以添加什么样的断言来testing这种方法? 当然,你可以尝试一下,但这只是代码膨胀。

该解决scheme为您提供JUnit本身。

在没有例外的情况下,你想明确地说明这种行为,只需添加预期如下:

 @Test(expected = Test.None.class /* no exception expected */) public void test_printLine() { Printer.printLine("line"); } 

Test.None.class是预期值的默认值。

使用AssertJstream利断言3.7.0 :

 Assertions.assertThatCode(() -> toTest.method()) .doesNotThrowAnyException(); 

如果你想testing你的testing目标是否消耗这个exception。 只要离开testing(模拟合作者使用jMock2):

 @Test public void consumesAndLogsExceptions() throws Exception { context.checking(new Expectations() { { oneOf(collaborator).doSth(); will(throwException(new NullPointerException())); } }); target.doSth(); } 

如果您的目标确实消耗了抛出的exception,testing会通过,否则testing将失败。

如果你想testing你的exception消耗逻辑,事情变得更加复杂。 我build议把这个消费委托给一个可以被嘲笑的合作者。 因此,testing可能是:

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

但有时如果你只是想logging它,它是过度devise的。 在这种情况下,这篇文章( http://java.dzone.com/articles/monitoring-declarative-transac,http://blog.novoj.net/2008/09/20/testing-aspect-pointcuts-is-there在这种情况下,如果你坚持tdd,可能会有帮助。;

使用assertNull(…)

 @Test public void foo() { try { //execute code that you expect not to throw Exceptions. } catch (Exception e){ assertNull(e); } } 

以下检查失败,检查或取消选中所有exception:

 @Test public void testMyCode() { try { runMyTestCode(); } catch (Throwable t) { throw new Error("fail!"); } } 

@Test注释提供了预期的参数。

 @Test(expected=IllegalArgumentException.class) public void foo() { // Your test --> Will fail if not IlleArgumentException is thrown } 

您可以期望通过创build规则不引发exception。

 @Rule public ExpectedException expectedException = ExpectedException.none();