【问题标题】:Scala test dependent methods used to calculate vals are executed only once用于计算 val 的 Scala 测试相关方法仅执行一次
【发布时间】:2020-04-11 18:49:44
【问题描述】:

我是 scala 的新手,我正在尝试找出测试以下过程的最佳方法。

我有一个从构造函数参数中获取数字列表的类。该类支持对列表的各种操作,一些操作可能依赖于其他操作的输出。但是每个选项都应该只根据需要执行计算,并且最多应该执行一次。构造函数中不应进行任何计算。

类定义示例 .
输入列表:列表[Int]。

x:返回 InputList 中所有元素的平方的向量。

y:返回 x 中所有元素的总和。

z:返回 y 的平方根。

至于类实现,我想我能够想出一个合适的解决方案,但现在我不知道如何测试依赖的操作树的计算只执行一次。

类实现方法#1:

class Operations(nums: List[Int]) {
  lazy val x: List[Int] = nums.map(n => n*n)
  lazy val y: Int = x.sum
  lazy val z: Double = scala.math.sqrt(y)
}

这是我的第一个方法,我有信心可以完成这项工作,但无法弄清楚如何正确测试它,所以我决定添加一些辅助方法来确认它们被称为只是一个

类实现方法#2:

class Ops(nums: List[Int]) {

  def square(numbers: List[Int]): List[Int] = {
    println("calling square function")
    numbers.map(n => n*n)
  }

  def sum(numbers: List[Int]): Int = {
    println("calling sum method")
    numbers.sum
  }

  def sqrt(num: Int): Double = {
    println("calling sqrt method")
    scala.math.sqrt(num)
  }

  lazy val x: Vector[Double] = square(nums)
  lazy val y: Double = sum(x)
  lazy val z: Double = sqrt(y)
}

我现在可以确认每个方法的每个依赖方法在必要时只调用一次。

现在我该如何为这些进程编写测试。我看过一些关于 mockito 的帖子并查看了文档,但找不到我想要的东西。我看了以下内容:

展示了如何测试一个函数是否被调用一次,然后如何测试其他依赖函数是否被调用? http://www.scalatest.org/user_guide/testing_with_mock_objects#mockito

Mockito: How to verify a method was called only once with exact parameters ignoring calls to other methods?

看起来很有希望,但我不知道语法:

https://github.com/mockito/mockito-scala

我要执行的示例测试

var listoperations:Ops = new Ops(List(2,4,4))
listoperations.y // confirms 36 is return, confirms square and sum methods were called just once
listoperations.x // confirms List(4,16,16) and confirms square method was not called
listoperations.z // confirms 6 is returned and sqrt method called once and square and sum methods were not called.

【问题讨论】:

  • 我要退后一步,你为什么要测试这些方法相互调用多少次?
  • 假设您必须处理一个非常大的列表,并且如果您调用更高级别的函数,您的代码应该能够重用您所做的任何计算。例如,如果您先调用 z,然后调用 x,则代码不应进行任何重新计算。我希望我的测试用例始终检查此行为以保证此性能

标签: scala mockito scalatest scalamock


【解决方案1】:

好的,让我们再讨论一下未成熟的优化参数。

Mocks 旨在用于存根/验证与代码(也称为其他类)的依赖项的交互,而不是检查它的内部,所以为了实现你想要的,你需要这样的东西

class Ops {
 def square(numbers: List[Int]): List[Int] = numbers.map(n => n*n)
 def sum(numbers: List[Int]): Int = numbers.sum
 def sqrt(num: Int): Double = scala.math.sqrt(num)
}

class Operations(nums: List[Int])(implicit ops: Ops) {
 lazy val x: List[Int] = ops.square(nums)
 lazy val y: Int = ops.sum(x)
 lazy val z: Double = ops.sqrt(y)
}

import org.mockito.{ ArgumentMatchersSugar, IdiomaticMockito}

class IdiomaticMockitoTest extends AnyWordSpec with IdiomaticMockito with ArgumentMatchersSugar
  "operations" should {
    "be memoised" in {
      implicit val opsMock = spy(new Ops)
      val testObj = new Operations(List(2, 4, 4))

      testObj.x shouldBe List(4, 16, 16)
      testObj.y shouldBe 36
      testObj.y shouldBe 36 //call it again just for the sake of the argument
      testObj.z shouldBe 6 //sqrt(36)
      testObj.z shouldBe 6 //sqrt(36), call it again just for the sake of the argument

      opsMock.sum(*) wasCalled once
      opsMock.sqrt(*) wasCalled once
    }
  }
}

希望这是有道理的,你提到你是 scala 的新手,所以我不想对 implicits 太疯狂所以这是一个非常基本的例子,你原来的 Operations 类的 API 是相同,但它将繁重的工作交给了可以模拟的第三方,以便您验证交互。

【讨论】:

  • 这正是我想要的,但我似乎无法得到 sum(*)、wasCalled 和一次工作。我尝试使用 Mockito.spy,但是当我使用星号或尝试调用“wasCalled”时,它无法解决符号错误。这些方法是否来自不同的包?
  • 是的,mockito-scala 完全从 mockito-core 抽象出你,所以你不应该使用 org.mockito.Mockito 对象或任何其他核心/java 类,mockito-scala 中的模式类似于scalatest,你混合了你需要的特征(我已经更新了例子来展示这一点)。如果它解决了您的问题,请考虑将答案标记为正确的答案:)
【解决方案2】:

正如你提到的那样,Mockito 是要走的路,这里有一个例子:

class NumberOPSTest extends FunSuite with Matchers with Mockito {

  test("testSum") {
    val listoperations = smartMock[NumberOPS]
    when(listoperations.sum(any)).thenCallRealMethod()

    listoperations.sum(List(2, 4, 4)) shouldEqual 10

    verify(listoperations, never()).sqrt(any)
  }

}

【讨论】:

  • 嗨,谢谢你的建议,这看起来像我需要的,但我不知道 smarkMock 来自哪里。还有一个有点切题的问题。我注意到几个示例中的语法 mock[className] 但是当类的默认构造函数需要参数时会发生什么?
  • 你也嘲笑它,when(mainObject.getAttribute("search")).thenReturn(someObject) 确保你接受你的问题的答案
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-09-04
  • 2011-02-13
  • 2020-10-11
  • 2015-02-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多