【问题标题】:Mocking MessageDigest.getInstance() to throw an exception模拟 MessageDigest.getInstance() 以引发异常
【发布时间】:2011-05-18 23:11:08
【问题描述】:

我得到了以下方法:

private MessageDigest getMessageDigest() {
    try {
        return MessageDigest.getInstance("MD5");
    } catch (NoSuchAlgorithmException e) {
        throw new Error(e);
    }
}

要获得 100% 的代码覆盖率,我需要进入 catch 块。但我绝对不确定我该怎么做。在这种情况下,是否有一些模拟框架可以帮助我?如果是这样 - 如何?或者有没有其他方法无需捕获异常?

【问题讨论】:

    标签: java unit-testing mocking


    【解决方案1】:

    MessageDigest 上的 getInstance 方法看起来像一个静态方法。不能模拟静态方法。我同意 ratchet 的观点,你不应该以 100% 的代码覆盖率为目标,而应该专注于测试复杂代码的领域。

    【讨论】:

      【解决方案2】:

      我会这样写:

      try {
          return MessageDigest.getInstance("MD5");
      } catch (NoSuchAlgorithmException e) {
          throw (AssertionError)new AssertionError("unreachable").initCause(e);
      }
      

      并声明因为catch块不可达,所以不需要测试。

      【讨论】:

        【解决方案3】:

        老实说,在这种情况下,您不需要覆盖无法访问的样板代码,以确保您不必担心用户代码中的已检查异常(如果您能解释的话,大多数情况下 98% 的覆盖率就足够了为什么这 2% 被错过了)

        【讨论】:

          【解决方案4】:

          只是为了跟进这个问题,可以通过PowerMock 完成。

          作为摘录,这是我的工作代码:

          @RunWith(PowerMockRunner.class)
          @PrepareForTest({MyClass.class, MessageDigest.class})
          public class MyClassTest {
          
              private MyClass myClass = new MyClass();
              @Mock private MessageDigest messageDigestMock;
          
              @Test
              public void shouldDoMethodCall() throws Exception {
                  setupMessageDigest();
          
                  String value = myClass.myMethodCall();
          
                  // I use FestAssert here, you can use any framework you like, but you get
                  // the general idea
                  Assertions.assertThat(value).isEqualToIgnoringCase("hashed_value");
              }
          
              public void setupMessageDigest() throws Exception {
                  PowerMockito.mockStatic(MessageDigest.class);
                  when(MessageDigest.getInstance("SHA1")).thenReturn(messageDigestMock);
                  when(messageDigestMock.digest(Matchers.<byte[]>anyObject())).thenReturn("hashed_value".getBytes());
              }
          
          }
          

          “MyClass”类将简单地执行以下操作:

          public class MyClass {
          
              public String myMethodCall() {
          
                  return new String(MessageDigest.getInstance("SHA1").digest("someString".getBytes()));
          
              }        
          
          }
          

          在额外的测试中,你可以写

          when(MessageDigest.getInstance("SHA1")).thenThrow(new NoSuchAlgorithmException());
          

          不是我提到的返回,而是进入你的 catch 块。

          但请注意,使用 PowerMock 有一些缺点。它通常会使用更多的内存和更多的实例化时间,因此您的测试会运行更长时间。对于这个特定的测试,它不会有很大的不同,但只是作为一个抬头。

          【讨论】:

            【解决方案5】:

            您的异常无法访问,因为该异常永远不会被抛出。我想用Mockito 做类似的事情是合乎逻辑的:

            doThrow(new NoSuchAlgorithmException()).when(MessageDigest.getInstance("MD5")); // this is psuedo code
            

            但这仍然没有多大意义。您最好编写如下代码:

            private static final MessageDigest MD5_DIGEST;
            static {
               try {
                  MD5_DIGEST = MessageDigest.getInstance("MD5");
               ///CLOVER:OFF
               } catch (Exception e) {
                  // can't happen since MD5 is a known digest
               }
               ///CLOVER:ON
            }
            
            public MessageDigest getMessageDigest() {
               return MD5_DIGEST;
            }
            

            否则您需要修改您的方法以使其可测试:

            public MessageDigest getMessageDigest(String digest) throws NoSuchAlgorithmException {
               return MessageDigest.getInstance(digest);
            }
            

            【讨论】:

              【解决方案6】:

              就我而言,我们的管道需要 100% 的覆盖率,所以我做了以下操作:

              • 定义一个静态内部类,只有一个静态方法返回MessageDigest的实例
              • 像@TomAnderson 那样定义方法:在catch 子句中,抛出AssertionError("unreachable", e) 表示绝对不可能到达这里
              • 对于jacocoTestReportjacocoTestCoverageVerification 任务,忽略jacoco.gradle 中的这个静态类。要知道如何排除内部类,请查看我的另一篇文章:How to ignore inner static classes in Jacoco when using Gradle (链接到如何在 Maven 中执行此操作的另一篇文章,以防您使用它)

              我将方法提取到类中,因为 Gradle 没有一致的语法来忽略类中的成员。检查Filtering options of Jacocohere

              【讨论】:

                【解决方案7】:

                您可以创建一个包装器 MessageDigest 类:

                @Component
                public class MessageDigest {
                   public java.security.MessageDigest getInstance(String algorithm) throws NoSuchAlgorithmException {
                    return java.security.MessageDigest.getInstance(algorithm);
                   }
                }
                

                【讨论】:

                  猜你喜欢
                  • 2023-03-07
                  • 2023-03-27
                  • 2014-02-15
                  • 2015-04-03
                  • 2017-04-05
                  • 2019-07-31
                  • 1970-01-01
                  • 2021-04-12
                  • 1970-01-01
                  相关资源
                  最近更新 更多