如何测试没有抛出异常?

时间:2013-07-18 18:27:45

标签: java unit-testing junit exception-handling junit4

我知道一种方法是:

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

17 个答案:

答案 0 :(得分:165)

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

我注意到这个问题不时引起人们的兴趣,所以我会稍微扩展一下。

单元测试的背景

当您进行单元测试时,重要的是要自己定义您认为的工作单元。基本上:提取您的代码库,可能包含也可能不包含代表单一功能的多个方法或类。

或者,如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 :(得分:58)

我偶然发现了这一点,因为SonarQube的规则是“squid:S2699”:“在这个测试案例中添加至少一个断言。”

我进行了一项简单的测试,其唯一的目标是在不抛出异常的情况下完成。

考虑这个简单的代码:

public class Printer {

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

可以添加什么样的断言来测试这种方法? 当然,你可以试一试它,但这只是代码臃肿。

解决方案来自JUnit本身。

如果没有抛出异常并且您想明确说明此行为,只需添加expected,如下例所示:

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

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

答案 2 :(得分:29)

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工具:aribitrary代码块和简单的注释

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

我还建议您使用TheoriesParameterizedinput上对此测试进行参数化,以便您可以更轻松地将此测试重新用于其他输入。或者,如果你想要异国情调,你可以选择test-generation tool(和this)。 TestNG更好地支持参数化测试。

我觉得特别令人不愉快的是使用@Test(expectedException=IllegalArgumentException.class)的建议,此例外情况非常广泛。如果你的代码发生了变化,导致测试中的组件的构造函数为if(constructorArgument <= 0) throw IllegalArgumentException(),并且你的测试为该参数提供了0,因为它很方便 - 而且这很常见,因为良好的生成测试数据是一个令人惊讶的难题 - - ,那么你的测试将是绿色条,即使它没有测试。这样的测试比没用更糟糕。

答案 3 :(得分:17)

尽管此职位现在已有6年历史,但Junit世界已经发生了很多变化。使用Junit5,您现在可以使用

org.junit.jupiter.api.Assertions.assertDoesNotThrow()

例如:

public void thisMethodDoesNotThrowException(){
   System.out.println("Hello There");
}

@Test
public void test_thisMethodDoesNotThrowException(){
  org.junit.jupiter.api.Assertions.assertDoesNotThrow(
      ()-> thisMethodDoesNotThrowException()
    );
}

希望它会帮助使用更高版本的Junit5的人

答案 4 :(得分:16)

如果您不幸遇到代码中的所有错误。 你可以愚蠢地做

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

答案 5 :(得分:16)

使用AssertJ fluent assertions 3.7.0

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

答案 6 :(得分:7)

JUnit 5(木星)提供了三个功能来检查是否存在异常:

assertAll​()

所有提供的

声明 executables
不要抛出异常。

assertDoesNotThrow​()

断言表示执行
提供了executable / supplier
不会抛出任何类型的exception

此功能可用
JUnit 5.2.0(2018年4月29日)以来。

assertThrows​()

声明表示已执行所提供的executable
抛出 expectedType的异常
并返回exception

示例

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

}

答案 7 :(得分:5)

JUnit5为此目的添加了assertAll()方法。

assertAll( () -> foo() )

source: JUnit 5 API

答案 8 :(得分:2)

要使用诸如void方法之类的场景来测试

void testMeWell() throws SomeException {..}

不抛出异常:

Junit5

assertDoesNotThrow(() -> {
    testMeWell();
});

答案 9 :(得分:2)

您可以使用@Rule然后调用方法reportMissingExceptionWithMessage来执行此操作,如下所示: 这是Scala,但它可以在Java中轻松完成。

enter image description here

答案 10 :(得分: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-transachttp://blog.novoj.net/2008/09/20/testing-aspect-pointcuts-is-there-an-easy-way/)可能有所帮助。

答案 11 :(得分:1)

您可以预期创建规则不会引发异常。

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

答案 12 :(得分:1)

使用assertNull(...)

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

答案 13 :(得分:0)

这可能不是最好的方法,但是它可以确保不会从正在测试的代码块中引发异常。

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

答案 14 :(得分:0)

您可以基于junit的断言创建任何类型的断言:

static void assertDoesNotThrow(Executable executable) {
    assertDoesNotThrow(executable, "must not throw");
}
static void assertDoesNotThrow(Executable executable, String message) {
    try {
        executable.execute();
    } catch (Throwable err) {
        fail(message);
    }
}

并测试:

//the following will succeed
assertDoesNotThrow(()->methodMustNotThrow(1));
assertDoesNotThrow(()->methodMustNotThrow(1), "fail with specific message: facepalm");
//the following will fail
assertDoesNotThrow(()->methodMustNotThrow(2));
assertDoesNotThrow(()-> {throw new Exception("Hello world");}, "Fail: must not trow");

通常来说,有可能在任何情况下,在有意义的任何地方立即使测试失败(“ bla bla bla”)。例如,如果在测试用例中抛出任何异常,请在try / catch块中使用它失败:

try{methodMustNotThrow(1);}catch(Throwable e){fail("must not throw");}
//or
try{methodMustNotThrow(1);}catch(Throwable e){Assertions.fail("must not throw");}

这是我们测试的方法的示例,假设我们有这样一种方法,在特定情况下一定不能失败,但它可能会失败:

void methodMustNotThrow(int x) throws Exception{
    if (x == 1) return;
    throw new Exception();
}

以上方法是一个简单的示例。但这适用于故障不太明显的复杂情况。 有进口:

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.function.Executable;
import static org.junit.jupiter.api.Assertions.*;

答案 15 :(得分:0)

我面临着同样的情况,我需要检查是否应该在仅当应该抛出异常时抛出该异常。 最终,使用以下代码使异常处理对我有好处:

    try {
        functionThatMightThrowException()
    }catch (Exception e){
        Assert.fail("should not throw exception");
    }
    RestOfAssertions();

对我来说,主要好处是它很简单,并且在相同的结构中检查“是否且仅当”的另一种方法确实很容易

答案 16 :(得分:-1)

以下内容未通过测试,未检查或未选中所有例外:

@Test
public void testMyCode() {

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