如何测试没有抛出异常?

Ank*_*gra 206 java junit unit-testing 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");
   }
}
Run Code Online (Sandbox Code Playgroud)

有没有更清洁的方法来做到这一点.(可能使用Junit的@Rule?)

Jer*_*vel 183

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

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

单元测试的背景

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

或者,如Roy Osherove的第二版"单元测试艺术"中所定义,第11页:

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

重要的是要认识到,一个工作单元通常不仅仅是一种方法,而是在最基本的层面上它是一种方法,之后它被其他工作单元封装.

在此输入图像描述

理想情况下,您应该为每个单独的工作单元设置一个测试方法,这样您就可以随时立即查看出错的地方.在这个例子中,有一个基本的方法getUserById(),它将返回一个用户,总共有3个单位的作品.

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

一旦你处理了这个基本工作单元的所有用例,你就提升了一个级别.在这里你做的完全相同,但你只处理来自当前水平下方的水平的异常.这可以使您的测试代码保持良好的结构,并允许您快速浏览架构以找出出错的地方,而不必遍布整个地方.

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

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

当您使用有效输入时,您将设置隐含的期望值,即您编写的任何测试都将起作用.

这样的方法调用看起来像这样:existingUserById_ShouldReturn_UserObject.如果此方法失败(例如:抛出异常),那么您就知道出现了问题,您可以开始挖掘.

通过添加另一个nonExistingUserById_ShouldThrow_IllegalArgumentException使用错误输入并期望异常的test(),您可以看到您的方法是否完成了错误输入的操作.

TL; DR

你试图在测试中做两件事:检查有效和错误的输入.通过将其分成两个方法,每个方法都做一件事,您将获得更清晰的测试,并更好地了解出错的地方.

通过记住工作的分层单元,您还可以减少层次结构中较高层的所需测试数量,因为您不必考虑下层中可能出错的所有事物:当前层之下的层是虚拟保证,您的依赖项可以工作,如果出现问题,它就在您当前的层中(假设较低层本身不会抛出任何错误).

  • 您是说您的功能取决于异常的处理?这是一种代码味道:优雅的例外是让你抓住问题; 它们不用于流量控制.如果要测试应该抛出异常的场景,那么应该使用`expected`注释.如果要测试代码失败的场景,并且想要查看错误是否得到正确处理:使用`expected`并使用asserts来确定它是否已被解析. (6认同)
  • @JeroenVannevel测试是否正确处理导致异常抛出的错误情况是完全有效的. (4认同)
  • 问题是我正在尝试TDD,而我使用的合作者之一抛出了异常。因此,我需要测试以下事实:我正在使用协作者抛出的异常 (2认同)

Sve*_*ing 99

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

我有一个简单的测试,其唯一的目标是通过而不抛出异常.

考虑这个简单的代码:

public class Printer {

    public static void printLine(final String line) {
        System.out.println(line);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

解决方案来自JUnit本身.

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

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

Test.None.class 是期望值的默认值.

  • 我认为这是最好的答案.接受的答案很棒,作者指出代码气味是正确的.但他并没有真正回答具体问题. (26认同)
  • @oziomajnr 仅用“@Test”注释该方法对解决 SonarQube 问题没有任何帮助。 (7认同)
  • 有趣的是,预期的默认值是 None,因此只需使用 @Test 注释该方法即可。 (6认同)
  • 为 Junit5 添加相同的解决方案: `assertDoesNotThrow(() -> Printer.printLine("line"));` (4认同)

Gro*_*tav 30

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 }
Run Code Online (Sandbox Code Playgroud)

然后你的代码变得简单:

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

如果你不能访问Java-8,我会使用一个痛苦的旧java工具:aribitrary代码块和一个简单的注释

//setup
Component component = new Component();

//act
configure(component);

//assert 
/*assert does not throw*/{
  component.doSomething();
}
Run Code Online (Sandbox Code Playgroud)

最后,用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()
}
Run Code Online (Sandbox Code Playgroud)

虽然有很多空间可以摆弄你想要表达的方式,但我总是喜欢流利的断言.


关于

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

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

Java允许控制流的异常.这是由JRE运行时本身在API中完成的,例如Double.parseDouble通过a NumberFormatExceptionPaths.getvia InvalidPathException.

鉴于你已经编写了一个验证Number字符串的组件Double.ParseDouble,可能使用了Regex,也许是一个手写的解析器,或者是嵌入了一些其他域规则的东西,这些规则将double的范围限制为特定的,最好如何测试这个零件?我认为一个明显的测试是断言,当解析结果字符串时,不会抛出任何异常.我会使用上面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));
}
Run Code Online (Sandbox Code Playgroud)

我还鼓励您input使用Theories或参数化此测试,Parameterized以便您可以更轻松地将此测试重新用于其他输入.或者,如果你想变得异国情调,你可以选择一个测试生成工具(和这个).TestNG更好地支持参数化测试.

我发现特别令人不愉快的是使用的建议@Test(expectedException=IllegalArgumentException.class),这个例外是非常危险的.如果您的代码发生变化,导致测试组件的构件已经存在if(constructorArgument <= 0) throw IllegalArgumentException(),并且您的测试为该参数提供了0,因为它很方便 - 这很常见,因为良好的生成测试数据是一个非常难的问题 - 然后您的测试即使它没有测试任何东西,它将是绿色条.这样的测试比没用更糟糕.

  • (关于预期异常的使用)从JUnit 4.13开始,您可以使用`Assert.assertThrows`来检查某些代码是否抛出异常。 (2认同)

den*_*enu 30

使用AssertJ流畅的断言3.7.0:

Assertions.assertThatCode(() -> toTest.method())
    .doesNotThrowAnyException();
Run Code Online (Sandbox Code Playgroud)

  • 面向junit4用户的最简单解决方案,谢谢 (2认同)

oli*_*bre 21

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

assertAll?()

断言所有供应executables
  并不会抛出异常。

assertDoesNotThrow?()

断言
  提供的executable/的supplier
执行不会引发任何异常

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

assertThrows?()

断言所提供的执行将executable
引发的异常expectedType
  并返回该异常

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

}
Run Code Online (Sandbox Code Playgroud)

  • 这就是现在最好的答案。其他答案正在讨论旧版本的 JUnit (10认同)
  • 您现在可以执行以下操作:``assertDoesNotThrow(myObject::myValidationFunction);``` (4认同)

Din*_*ora 20

虽然这篇文章已经 6 岁了,但是,在 Junit 世界中发生了很多变化。使用 Junit5,您现在可以使用

org.junit.jupiter.api.Assertions.assertDoesNotThrow()
Run Code Online (Sandbox Code Playgroud)

前任:

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

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

希望它能帮助使用较新版本 Junit5 的人


Ben*_*son 18

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

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

  • 这不是一个很好的解决方案.如果不应抛出异常的方法抛出,则不会收到有用的错误消息.只需调用不应抛出异常的方法,并测试它的返回值(或副作用,如记录异常).如果它后来意外地抛出异常,则测试将失败. (4认同)
  • 或者只是将Assert.fail()放入捕获,更容易和更漂亮的IMO. (3认同)
  • 只是一个小建议,“Exception ex”应该是“= null;”,然后才能测试它。 (2认同)

小智 8

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

assertAll( () -> foo() )
Run Code Online (Sandbox Code Playgroud)

来源:JUnit 5 API


pir*_*rho 7

使用 void 方法测试场景,例如

void testMeWell() throws SomeException {..}
Run Code Online (Sandbox Code Playgroud)

为了不抛出异常:

朱尼特5

assertDoesNotThrow(() -> {
    testMeWell();
});
Run Code Online (Sandbox Code Playgroud)


Fil*_*tto 5

AssertJ 可以处理这种情况:

assertThatNoException().isThrownBy(() -> System.out.println("OK"));
Run Code Online (Sandbox Code Playgroud)

检查文档以获取更多信息https://assertj.github.io/doc/#assertj-core-exception-assertions-no-exception