用于计算 val 的 Scala 测试依赖方法仅执行一次

Scala test dependent methods used to calculate vals are executed only once

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

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

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

x: returns 一个向量,其中包含 InputList 中所有元素的平方。

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

z:returns y 的平方根。

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

Class 实施方法#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)
}

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

Class 实施方法#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

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

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.

正如您提到的,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)
  }

}

好吧,让我们把过早优化的论点留到下一次。

模拟旨在用于 stub/verify 与代码依赖项(又名其他 classes)的交互,而不是检查它的内部,所以为了实现你想要的需要这样的东西

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 太疯狂所以这是一个非常基本的例子,其中 API您原来的 Operations class 是相同的,但是它将繁重的工作提取给了可以模拟的第三方,以便您可以验证交互。