【问题标题】:How to test that no exception is thrown?如何测试没有抛出异常?
【发布时间】:2013-07-17 20:41:41
【问题描述】:

我知道一种方法是:

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

【问题讨论】:

  • 如果 JUnit 测试抛出除预期异常之外的任何异常,则判断它失败。通常没有例外。
  • JUnit中没有失败和错误的区别吗?第一个表示测试失败,第二个表示发生了意外。
  • @Vituel 是的,有,而且这种区别在 NetBeans 中非常明显。错误为红色,失败为黄色。
  • @Raedwald 在截止日期的压力下我会这样看。但是在世界上所有的时间里,我不想从红色变成绿色而中间没有黄色。

标签: java unit-testing exception junit junit4


【解决方案1】:

您以错误的方式处理此问题。只需测试您的功能:如果抛出异常,测试将自动失败。如果没有抛出异常,您的测试将全部变为绿色。

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

单元测试背景

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

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

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

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

理想情况下,您应该为每个单独的工作单元设置一个测试方法,以便您始终可以立即查看哪里出了问题。在本例中,有一个名为getUserById() 的基本方法,它将返回一个用户,总共有 3 个工作单元。

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

一旦您处理了这个基本工作单元的所有用例,您就可以提升一个级别。在这里您所做的完全相同,但您只处理来自当前级别正下方的异常。这可以使您的测试代码保持良好的结构,并让您可以快速运行架构以找出问题所在,而不必到处乱跑。

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

此时应该清楚我们将如何处理这些异常。输入有2种:valid输入和faulty输入(严格意义上的输入有效,但不正确)。

当您使用 valid 输入时,您正在设置隐含的期望,即无论您编写什么测试,都将起作用。

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

通过添加另一个使用 faulty 输入并期望出现异常的测试 (nonExistingUserById_ShouldThrow_IllegalArgumentException),您可以查看您的方法是否按照错误输入执行了它应该执行的操作。

TL;DR

您在测试中尝试做两件事:检查有效输入和错误输入。通过将其拆分为两个方法,每个方法都做一件事,您将获得更清晰的测试,并更好地了解哪里出了问题。

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

【讨论】:

  • 问题是我正在尝试 TDD,而我使用的合作者之一正在抛出异常。所以我需要测试我正在消费协作者抛出的异常这一事实
  • 你是说你的功能依赖于对异常的处理吗?那是代码的味道:异常可以优雅地让你发现问题;它们不用于流量控制。如果你想测试一个应该抛出异常的场景,那么你应该使用expected 注释。如果您想测试代码失败的场景并且想查看错误是否得到正确处理:使用expected 并可能使用断言来确定它是否已解决。
  • @JeroenVannevel 测试导致抛出异常的错误情况是否得到正确处理是完全有效的。
  • @dpk 是的,你可以。您将throws IllegalArgumentException 添加到您的测试中。你最终想要的是,如果有异常,你的测试会变成红色。好吧,你猜怎么着?你不需要写fail()。正如@Jeroen Vannevel 所写:“如果抛出异常,测试将自动失败。”
  • @Nir 提出了相关的观点。在测试更高级别之前测试到失败的核心级别时,当较低级别在外部包中失败时,该原理就会崩溃。从被测应用程序的角度来看,“执行此操作时包失败”才是最重要的,需要在包中添加内部测试
【解决方案2】:

如果您想测试您的测试目标是否使用异常。只需将测试保留为(使用 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/)可能会有所帮助。

【讨论】:

    【解决方案3】:

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

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

    【讨论】:

    • 只是一个小建议,Exception ex 应该是= null; 才可以测试。
    • 这不是一个很好的解决方案。如果不应该抛出异常的方法确实抛出了,您将不会收到有用的错误消息。只需调用不应抛出异常的方法,并测试它的返回值(或副作用,如记录异常)。如果稍后意外抛出异常,测试将失败。
    • 或者只是将 Assert.fail() 放在 catch 中,更简单更漂亮的 IMO。
    • 是的,我同意你的看法。另一种方法是在方法 @Test (expected = InvalidRequestException.class) 之上添加注释
    • 这对我很有用,谢谢。在我的情况下,我在AwaitilityuntilAsserted(ThrowingRunnable assertion) 内断言。我调用的方法一开始总是会抛出异常,但我想断言它最终会停止这样做(根据 Awaitility 的参数)
    【解决方案4】:

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

    虽然有很多空间可以摆弄你想要如何表达这一点,但我一直是fluent assertions 的粉丝。


    关于

    您以错误的方式处理此问题。只需测试您的功能:如果抛出异常,测试将自动失败。如果没有抛出异常,您的测试将全部变为绿色。

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

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

    假设您已经编写了一个组件来验证 Double.ParseDouble 的数字字符串,可能使用正则表达式,可能是手写解析器,或者可能嵌入了一些其他域规则,这些规则将 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));
    }
    

    我还鼓励您在 input 上使用 TheoriesParameterized 参数化此测试,以便您可以更轻松地将此测试用于其他输入。或者,如果你想变得异国情调,你可以选择test-generation tool(和this)。 TestNG 对参数化测试有更好的支持。

    我觉得特别令人不快的是使用@Test(expectedException=IllegalArgumentException.class) 的建议,这个例外非常广泛。如果您的代码发生更改,使得被测组件的构造函数具有if(constructorArgument <= 0) throw IllegalArgumentException(),并且您的测试为该参数提供了 0,因为它很方便——这很常见,因为生成测试数据是一个非常困难的问题——,那么您的测试将是绿条,即使它没有测试任何内容。这样的测试比没用还糟糕。

    【讨论】:

    • (关于预期异常的使用)从JUnit 4.13开始,您可以使用Assert.assertThrows来检查某些代码是否抛出了异常。
    【解决方案5】:

    使用assertNull(...)

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

    【讨论】:

    • 我会说这是误导。永远不会到达 catch 块,因此也永远不会执行 assertNull。然而,快速阅读者会得到这样的印象,即做出了一个真正验证非抛出案例的断言。换句话说:如果到达 catch 块,异常总是非空的——因此它可以被一个简单的fail 替换。
    • 确实具有误导性,......但是等等,......哦,我明白了......assertNull(e) 将报告测试失败,如所述e 不能是@987654327 @ 在catch 块中... Mike 这只是奇怪的编程:-/ ...是的,至少像 Andreas 说的那样使用fail()
    【解决方案6】:

    以下所有异常的测试都失败了,检查或未检查:

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

    【讨论】:

      【解决方案7】:

      AssertJ fluent assertions 3.7.0:

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

      【讨论】:

        【解决方案8】:

        由于 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 是预期值的默认值。

        如果你import org.junit.Test.None,那么你可以写:

        @Test(expected = None.class)
        

        您可能会发现它更具可读性。

        【讨论】:

        • 我认为这是最好的答案。接受的答案很好,作者指出代码异味是正确的。但是他并没有真正回答具体的问题。
        • 有趣的是,expected 的默认值是 None,因此只需使用 @Test 注释方法即可。
        • @oziomajnr 只是用 @Test 注释方法对解决 SonarQube 问题没有任何帮助。
        • 这与我遇到的问题完全相同。虽然我完全同意 @jeroen-vannevel 的回答,但我需要进行某种验证以确保 SonarQube 不会引发任何问题。
        • 为 Junit5 添加相同的解决方案:assertDoesNotThrow(() -> Printer.printLine("line"));
        【解决方案9】:

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

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

        【讨论】:

        • ExpectedExceptions 用于断言抛出的异常。您提供的代码只是为了初始化规则,以便您可以添加对断言的要求。这段代码本身并没有增加任何价值。 javadoc 还说明了这一点:“/** * 返回一个 {@linkplain TestRule 规则},它不希望抛出异常(与没有此规则的行为相同)。*/”因此它将具有与没有它完全相同的结果.
        • 我同意你的观点,不会那样使用它,但可以断言没有抛出异常。如果测试通过,应该足以说明没有引发异常,但另一方面,如果有问题,则必须有问题。很少但有时仍然可以看到它。如果代码和环境发生了变化,而我们没有针对某些特定的极端情况进行测试怎么办?
        • 我很想知道您将如何断言预期的异常。是的,如果需求发生变化并且您没有对特定的边缘案例进行测试,那么您就完蛋了 ;-) 始终涵盖所有角落案例。
        • 什么意思?你不断言它,你期望它。在这种情况下,您不会有任何例外。不知道你在说什么。
        【解决方案10】:

        您可以通过使用@Rule 然后调用方法reportMissingExceptionWithMessage 来做到这一点,如下所示: 这是 Scala 代码。

        【讨论】:

        • private val?这是什么语言?显然不是 Java ;p 请不要提供代码作为屏幕截图,不受欢迎。
        • 我看到你提到它是 Scala,但是说它可以在 Java 中轻松完成并不是一个强有力的论据,我很抱歉
        • 我删除了困扰你的部分。我也会尝试替换图像。还没想好怎么加代码..
        • ExpectedException.none() 已弃用。
        【解决方案11】:

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

        assertAll( () -> foo() )
        

        source: JUnit 5 API

        【讨论】:

          【解决方案12】:

          JUnit 5(Jupiter)提供了三个函数来检查异常是否存在:

          assertAll​()

          断言 all 提供了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));
              }
          
          }
          

          【讨论】:

          • 这是现在最好的答案。其他答案正在讨论旧版本的 JUnit
          • 链接有问题
          • 这是 spring boot 2.2.0+ 和 junit 5 的最佳答案
          • 您现在可以这样做:assertDoesNotThrow(myObject::myValidationFunction);
          • 完美答案!
          【解决方案13】:

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

          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】:

            您可以根据来自 junit 的断言创建任何类型的您自己的断言,因为这些断言是专门为创建用户定义的断言而设计的,旨在与 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);
                }
            }
            

            现在测试所谓的场景 methodMustNotThrow 并以 junit 样式记录所有失败:

            //test and log with default and custom messages
            //the following will succeed
            assertDoesNotThrow(()->methodMustNotThrow(1));
            assertDoesNotThrow(()->methodMustNotThrow(1), "custom facepalm");
            //the following will fail
            assertDoesNotThrow(()->methodMustNotThrow(2));
            assertDoesNotThrow(()-> {throw new Exception("Hello world");}, "message");
            //See implementation of methodMustNotThrow below
            

            一般来说,通过调用fail(someMessage),在任何情况下,在任何有意义的地方,都有可能立即使任何测试失败,这正是为此目的而设计的。例如,如果在测试用例中抛出任何东西,则在 try/catch 块中使用它会失败:

            try{methodMustNotThrow(1);}catch(Throwable e){fail("must not throw");}
            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.*;
            

            【讨论】:

            • 有更好的选择来检查断言没有被抛出,这不涉及创建自定义代码。 @Rule 就是其中之一
            • @Vargan 我已经指出了以 JUnit 设计的方式创建自己的断言的方法,特别是为了创建自己的断言。 JUnit 通过设计,特别是为此目的,提供了创建您自己的规则,使用尚未实现的断言扩展 JUnit 的行为。因为并非所有东西都在这个世界上实现了这些断言的工作方式与 JUnit 断言在通过或失败以及报告失败方面的工作方式相同。
            【解决方案15】:

            尽管这篇文章已经 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 的人有所帮助

            【讨论】:

            • 我希望有一种方法可以在这里指定具体的异常类。我必须在AwaitilityuntilAsserted(ThrowingRunnable assertion) 内执行此操作。被测系统当前正在我提供的 ThrowingRunnable 上引发特定异常,但我想给它一些时间,直到它停止这样做。但是,如果它会引发不同的异常,我希望测试立即失败。
            【解决方案16】:

            使用 void 方法测试场景

            void testMeWell() throws SomeException {..}
            

            不抛出异常:

            Junit5

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

            【讨论】:

              【解决方案17】:

              我遇到了同样的情况,我需要检查是否在应该抛出异常时,并且只在应该时抛出异常。 最终通过以下代码使用异常处理程序对我有利:

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

              对我来说主要的好处是它非常直接,并且在相同的结构中检查“当且仅当”的另一种方式真的很容易

              【讨论】:

              • 欢迎来到 SO。您的问题被标记为“迟到的答案”审核,因为该问题已有 7 年历史,还有 17 个其他答案。虽然您的答案可能会提供一些价值,但很晚的答案通常会被否决。
              【解决方案18】:

              偶然发现了这个问题,因为我创建了一些通用方法,例如

              @Test
              void testSomething() {
                 checkGeneric(anComplexObect)
              }
              

              https://newbedev.com/sonarqube-issue-add-at-least-one-assertion-to-this-test-case-for-unit-test-with-assertions 中提出了一些注释内容。

              解决方案要简单得多。将“checkGeneric”方法重命名为“assertGeneric”就足够了。

              @Test
              void testSomething() {
                assertGeneric(anComplexObect)
              }
              

              【讨论】:

                猜你喜欢
                • 2018-09-11
                • 2020-12-21
                • 1970-01-01
                • 1970-01-01
                • 2020-08-18
                • 2014-05-10
                • 2015-01-17
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多