1

我正在尝试使用 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,而翻译器代码不能接受这个 null。

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

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

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

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

there was one(mockParser).parse(file)

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

试图弄清楚这一点,我试图将那条线改为

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

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

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

4

3 回答 3

4

如果你还没有看过,可以查看 specs2 文档的mock 期望页面

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

唐编辑后编辑:

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

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

于 2011-09-02T20:38:30.317 回答
1

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

我需要做的是为模拟提供一个合理的默认返回值,其形式为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特征的来源和它所调用的东西之后。

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

我可能会创建一个测试支持方法来隐藏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]]

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

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

于 2011-09-03T13:44:42.223 回答
0

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

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

于 2011-09-03T10:46:41.897 回答