【问题标题】:Scala, Specs2, Mockito and null return valuesScala、Specs2、Mockito 和 null 返回值
【发布时间】:2011-09-02 18:53:34
【问题描述】:

我正在尝试使用 Specs2 和 Mockito 测试一些 Scala 代码。我对这三个人都比较陌生,并且对返回 null 的模拟方法有困难。

在下面(有一些名称更改)

  "My Component's process(File)" should  {

    "pass file to Parser" in new modules {
      val file = mock[File]
      myComponent.process(file)

      there was one(mockParser).parse(file)
    }

    "pass parse result to Translator" in new modules {
      val file = mock[File]
      val myType1 = mock[MyType1]

      mockParser.parse(file) returns (Some(myType1))
      myComponent.process(file)

      there was one(mockTranslator).translate(myType1)
    }

  }

“将文件传递给解析器”一直有效,直到我在 SUT 中添加了翻译器调用,然后因为 mockParser.parse 方法返回了一个 null,而翻译器代码不能接受它。

同样,“将解析结果传递给 Translator”直到我尝试在 SUT 中使用翻译结果。

这两种方法的真实代码永远不会返回 null,但我不知道如何告诉 Mockito 让期望返回可用的结果。

我当然可以通过在 SUT 中进行空检查来解决这个问题,但我宁愿不这样做,因为我确保永远不会返回空值,而是使用 OptionNoneSome

指向一个好的 Scala/Specs2/Mockito 教程会很棒,就像一个简单的例子来说明如何改变一行一样

there was one(mockParser).parse(file)

当它不处理空值时,使其返回允许在 SUT 中继续执行的东西。

想弄清楚这一点,我尝试将那行更改为

there was one(mockParser).parse(file) returns myResult

myResult 的值是我想要返回的类型。这给了我一个编译错误,因为它希望在那里找到 MatchResult 而不是我的返回类型。

如果重要的话,我使用的是 Scala 2.9.0。

【问题讨论】:

    标签: scala mockito specs


    【解决方案1】:

    如果你没有看过,你可以看一下specs2文档的mock expectation page

    在您的代码中,存根应该是mockParser.parse(file) returns myResult

    在 Don 编辑后编辑:

    有误会。你在第二个例子中的做法很好,你应该在第一个测试中做同样的事情:

    val file = mock[File]
    val myType1 = mock[MyType1]
    
    mockParser.parse(file) returns (Some(myType1))
    myComponent.process(file)
    there was one(mockParser).parse(file)
    

    使用 mock 进行单元测试的想法始终相同:解释您的 mock 如何工作(存根)、执行、验证。

    这应该回答了这个问题,现在是个人建议:

    大多数情况下,除非您想验证某些算法行为(在第一次成功时停止,以相反的顺序处理列表),否则您不应该在单元测试中测试期望。

    在您的示例中,process 方法应该“翻译事物”,因此您的单元测试应该关注它:模拟您的解析器和翻译器,存根它们并且只检查整个过程的结果。它的粒度不那么细,但单元测试的目标不是检查方法的每一步。如果你想改变实现,你不应该修改一堆单元测试来验证方法的每一行。

    【讨论】:

    • 我在存根中使用它进行第二次测试,但问题在于预期而不是存根。我将进行编辑以澄清。
    • 我现在已经完成了编辑,我也会查看您链接的页面。我想我以前看过它,但可能在之前的阅读中漏掉了一个重要线索。
    • 啊哈!从来没有想过简单地存根并期望使用相同的方法。但它有效,至少在 Mockito 上有效。在我看来,它没有一些框架......
    【解决方案2】:

    我已经设法解决了这个问题,尽管可能有更好的解决方案,所以我将发布我自己的答案,但不会立即接受。

    我需要做的是为模拟提供一个合理的默认返回值,以 org.mockito.stubbing.Answer<T> 的形式,返回类型为 T。

    我可以通过以下模拟设置做到这一点:

    val defaultParseResult = new Answer[Option[MyType1]] {
      def answer(p1: InvocationOnMock): Option[MyType1] = None
    }
    val mockParser = org.mockito.Mockito.mock(implicitly[ClassManifest[Parser]].erasure,
                             defaultParseResult).asInstanceOf[Parser]
    

    在浏览了 org.specs2.mock.Mockito 特征及其调用的东西的源代码之后。

    现在,解析返回None,而不是返回null,而不是被存根(包括在第一个测试中预期的时候),这允许测试通过,这个值在被测代码中使用。

    我可能会创建一个测试支持方法来隐藏 mockParser 分配中的混乱,并让我对各种返回类型做同样的事情,因为我需要在这个集合中具有几个返回类型的相同功能测试。

    我在org.specs2.mock.Mockito 中找不到对执行此操作的更短方式的支持,但也许这会激发 Eric 添加此类功能。很高兴有作者参与对话...

    编辑

    在进一步阅读源代码后,我想到我应该能够只调用该方法

    def mock[T, A](implicit m: ClassManifest[T], a: org.mockito.stubbing.Answer[A]): T = org.mockito.Mockito.mock(implicitly[ClassManifest[T]].erasure, a).asInstanceOf[T]
    

    org.specs2.mock.MockitoMocker 中定义,这实际上是我上面解决方案的灵感。但我无法弄清楚电话。 mock 相当过载,我所有的尝试似乎最终都调用了不同的版本并且不喜欢我的参数。

    所以看起来 Eric 已经已经包含了对此的支持,但我不明白如何获得它。

    更新

    我定义了一个包含以下内容的特征:

      def mock[T, A](implicit m: ClassManifest[T], default: A): T = {
        org.mockito.Mockito.mock(
          implicitly[ClassManifest[T]].erasure,
          new Answer[A] {
          def answer(p1: InvocationOnMock): A = default
        }).asInstanceOf[T]
      }
    

    现在通过使用该特征,我可以将我的模拟设置为

    implicit val defaultParseResult = None
    val mockParser = mock[Parser,Option[MyType1]]
    

    毕竟我不需要在这个特定的测试中更多地使用这个,因为为此提供一个可用的值可以让我的所有测试在被测代码中没有空检查的情况下工作。但在其他测试中可能需要它。

    我仍然对如何在不添加此特征的情况下处理此问题感兴趣。

    【讨论】:

    • 在最新的 1.7-SNAPSHOT 中,我添加了传递任何类型的 Mockito 设置的可能性,包括默认答案。使用该版本,您可以写:val mockParser = mock[Parser].defaultReturn(None) 如果您有更详细的答案,您可以写:val mockParser = mock[Parser].defaultAnswer((i: InvocationOnMock) => whatever(i))。文档可用here
    • 谢谢。我会看看那个。
    【解决方案3】:

    如果没有完整的话,很难说,但您能检查一下您尝试模拟的方法不是最终方法吗?因为在这种情况下,Mockito 将无法模拟它并返回 null。

    另一个建议是,当某些东西不起作用时,在标准 JUnit 测试中使用 Mockito 重写代码。然后,如果它失败了,您的问题可能最好由 Mockito 邮件列表中的某个人来回答。

    【讨论】:

    • 这不是最终的。模拟和存根确实有效,并且测试通过了在被测试代码中添加的空检查。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-08-13
    • 1970-01-01
    • 2011-09-06
    • 1970-01-01
    • 2016-08-26
    • 1970-01-01
    相关资源
    最近更新 更多