2013-07-18 125 views
109

我知道如何做这件事的办法是:如何测试没有异常抛出?

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

+6

JUnit测试判断,如果它抛出一个比预期的异常之外的任何异常都失败了。通常不会有例外。 – Raedwald

+0

JUnit中的失败和错误没有区别吗?第一种意思是测试失败,第二种意思是意外事件发生。 – Vituel

+0

可能的重复[如何测试如果不引发特定的异常?](http://stackoverflow.com/questions/8575653/how-can-i-test-if-a-particular-exception-is-not -thrown) –

回答

124

你正在接近这个错误的方式。只需测试您的功能:如果抛出异常,测试将自动失败。如果没有发生异常,您的测试将全部变为绿色。

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

背景单元测试

当你的单元测试它来定义自己是什么你认为工作单位是很重要的。基本上:提取代码库,可能包含或不包含表示单个功能块的多个方法或类。

或者,如图The art of Unit Testing, 2nd Edition by Roy Osherove定义,第11页:

单元测试被调用工作单元被测试,然后的代码的自动片检查关于单个最终结果的一些假设的单位。单元测试几乎总是使用单元测试框架编写的。它可以很容易编写并且运行速度很快。它值得信赖,可读和可维护。只要生产代码没有改变,它的结果就是一致的。

重要的是要意识到的是,工作之一单位通常不只是一个方法,但在最基层的是一个方法之后,它是由其他工作单元封装。

enter image description here

理想情况下,你应该有工作的每一个独立的单元测试方法,让您可以随时立即查看,事情会出错。在这个例子中有一个叫做getUserById()的基本方法,它将返回一个用户,并且总共有3个工作单元。

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

一旦您处理了这个基本工作单元的所有用例,就会向上移动一级。在这里你完全一样,但你只处理来自当前下方的异常。这使您的测试代码结构良好,并允许您快速浏览体系结构以查找出现问题的位置,而不必在整个位置跳转。

处理一个测试有效和错误的输入

在这一点上应该很清楚我们要如何处理这些异常。有两种输入类型:有效输入和错误输入(输入有严格意义上的有效,但不正确)。

当您使用有效的输入时,您可以设置隐含的期望值,无论您编写什么测试,都可以使用。

这样的方法调用可能如下所示:existingUserById_ShouldReturn_UserObject。如果此方法失败(例如:抛出异常),那么你知道出了问题,你可以开始挖掘。

通过添加一个使用故障输入和期待一个例外,你可以看到你的方法是否做了它应该与输入错误做另一个测试(nonExistingUserById_ShouldThrow_IllegalArgumentException)。

TL; DR

你试图做两件事情在您的测试:检查是否有有效的和错误的输入。通过把它分成两种方法,每种方法做一件事,你就会有更清晰的测试,并且更好地了解事情出错的地方。

通过保持作品的分层单元,您还可以减少层次结构中层次较高的图层所需的测试量,因为您不必考虑每个可能出现错误的东西低层:当前层下面的层是虚拟保证你的依赖性起作用,如果出现问题,它在你当前的层(假设低层本身不会抛出任何错误)。

+1

问题是我正在尝试TDD,并且我使用的其中一个协作者正在抛出一个异常。因此,我需要测试一下这个事实:我正在使用协作者抛出的异常 –

+5

您是否说您的功能取决于处理异常?这是一种代码异味:有例外可以让你优雅地捕捉问题;它们不用于流量控制。如果你想测试一个应该抛出异常的场景,那么你应该使用'expected'注解。如果您想测试代码失败的场景,并且想要查看错误是否得到正确处理,请使用'expected'并且可能使用断言来确定它是否已解决。 –

+0

问题是我无法从协作器中发生的异常中恢复,我所做的只是使用log.debug(“错误消息”)记录问题。因此,我不能作为catch block的一部分发生副作用,我可以断言。 –

1

如果要测试测试目标是否消耗该异常。刚刚离开的试验为(使用jMock2模拟合作者):

@Test 
public void consumesAndLogsExceptions() throws Exception { 

    context.checking(new Expectations() { 
     { 
      oneOf(collaborator).doSth(); 
      will(throwException(new NullPointerException())); 
     } 
    }); 

    target.doSth(); 
} 

,如果你的目标不消耗抛出的异常的测试将通过,否则测试将失败。

如果你想测试你的异常消耗逻辑,事情会变得更加复杂。我建议将这种消费委托给可能被嘲笑的合作者。因此,测试可能是:

@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/)可能会有所帮助。

14

如果您运气不好,无法捕捉代码中的所有错误。 你可以做愚蠢

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

只是一个小建议,'Exception ex'应该是'= null;'才可以测试它。 – Denees

+4

这不是一个很好的解决方案。如果不应该抛出异常的方法抛出,你不会得到一个有用的错误信息。只要调用不应抛出异常的方法,并测试它的返回值(或者副作用,如记录异常)。如果它后来意外抛出异常,则测试将失败。 – NamshubWriter

+3

或者只是把Assert.fail()放在捕捉,更简单,更漂亮的IMO中。 –

25

的Java 8使这是一个容易得多,并且科特林/斯卡拉更是如此。

我们可以写一个小工具类

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

虽然有很多的空间你到底要怎么表达这个摆弄,我一直的fluent assertions风扇。


关于

你接近这个错误的方式。只需测试您的功能:如果抛出异常,测试将自动失败。如果没有发生异常,您的测试将全部变为绿色。

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

Java允许控制流的异常。这通过JRE运行时本身在Double.parseDouble之类的API中通过NumberFormatExceptionPaths.get经由InvalidPathException完成。

鉴于你已经编写了一个验证Double.ParseDouble的数字字符串的组件,也许使用正则表达式,也许是手写的解析器,或者是嵌入了一些其他域规则,这些规则将双精度范围限制到某个特定的值,如何最好地测试这个组件?我认为一个明显的测试是断言,当分析结果字符串时,不会引发异常。我会使用上面的assertDoesNotThrow/*comment*/{code}块来编写该测试。喜欢的东西

@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。或者,如果你想去异国情调,你可以去test-generation tool(和this)。 TestNG对参数化测试有更好的支持。

我发现特别令人不快的建议是使用@Test(expectedException=IllegalArgumentException.class),这个例外是危险广泛的。如果你的代码发生了变化,使得测试组​​件的构造函数有if(constructorArgument <= 0) throw IllegalArgumentException(),并且你的测试为这个参数提供了0,因为它很方便 - 这是很常见的,因为好的生成测试数据是一个令人惊讶的难题 - 那么你的尽管它没有测试任何东西,但测试将是绿色条。这样的测试比无用的更糟糕。

+1

(关于预期异常的使用)上添加注释。由于JUnit 4.13,您可以使用'Assert.assertThrows'来检查某些代码是否引发异常。 – MageWind

2

使用assertNull(...)

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

我会说这是误导。 catch块永远不会到达,所以'assertNull'也不会被执行。然而,快速的读者会得到这样的印象,即断言是真正验证了不抛出的情况。换句话说:如果catch块已经到达,那么这个异常总是非空的 - 因此它可以被一个简单的'fail'替换。 – Andreas

+0

确实误导,.....但是等等,哦,我明白了......'assertNull(e)'会报告测试失败,正如所述'e'不能在'catch'块中为'null'...... Mike只是奇怪的编程: -/...是的,至少是使用'失败()'像安德烈亚斯说 – Julien

0

下未能通过测试的所有异常,选中或取消选中:

@Test 
public void testMyCode() { 

    try { 
     runMyTestCode(); 
    } catch (Throwable t) { 
     throw new Error("fail!"); 
    } 
} 
-1

@Test注解提供期望的参数。

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

我偶然发现了这一点,因为SonarQubes规则 “鱿鱼:S2699”: “至少一个断言加入本次测试的情况。”

我有一个简单的测试,那是唯一的目标,它通过无一例外。

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

public class Printer { 

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

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

该解决方案为您提供JUnit本身。

的情况下没有抛出异常,要明确说明这种行为只是添加的预期如下所示:

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

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

+4

我认为这是最好的答案。被接受的答案很好,作者指出代码的味道是正确的。但是他没有真正回答具体的问题。 – HellishHeat

+0

@SvenDöring如何在'testng'中指定这个 –

0

您可以期望通过创建规则不引发异常。

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

ExpectedExceptions用于断言抛出的异常。您提供的代码仅用于初始化规则,以便您可以添加对断言的要求。 这段代码本身并没有增加任何值。 javadoc也声明了这一点:“/ ** *返回一个{@linkplain TestRule规则},它不会抛出异常到 *(与没有此规则的行为相同) * /” 所以它会有完全相同的没有它的结果。 –

+0

我同意你的看法,不会那样使用它,但可以断言没有异常被抛出。如果测试通过,应该足以说明这个异常没有被抛出,但另一方面,如果有一个问题,那就一定需要它。很少但有时仍然很好,可以看到它。如果代码和情况发生了变化,我们还没有测试某些特定的边缘案例? – LazerBanana

+0

我很好奇,看你如何断言与预期的例外。是的,如果需求发生变化,并且您没有测试某个特定的边缘情况,那么您总是会覆盖所有角落案例。 –

1

您可以通过使用一个@rule做到这一点,然后调用方法reportMissingExceptionWithMessage如下图所示: 这是斯卡拉,但它可以很容易地在Java中同样完成。

enter image description here