用于计算VAL的依赖于Scala测试的方法只执行一次
我是scala新手,我正在尝试找出测试以下过程的最佳方法 我有一个类,它从构造函数参数中获取一个数字列表。该类支持列表上的各种操作,有些操作可能依赖于其他操作的输出。但每个选项只应按需执行计算,最多应执行一次。不应在构造函数中进行任何计算 示例类定义。 输入列表:列表[Int] x:返回一个向量,其中包含InputList中所有元素的平方 y:返回x中所有元素的总和 z:返回y的平方根 至于类实现,我想我能够想出一个合适的解决方案,但现在我不知道如何测试依赖操作树的计算只执行一次 课程实施方法1: 这是我的第一个方法,我相信它可以完成这项工作,但是我不知道如何正确地测试它,所以我决定添加一些helper方法来确认它们只被称为helper方法 课程实施方法2: 我现在可以确认每个方法的每个依赖方法在必要时只调用一次 现在我如何为这些进程编写测试。我看到了一些关于mockito的帖子,并查看了文档,但没有找到我想要的内容。我看了以下几点: 演示如何测试函数是否被调用一次,然后如何测试其他函数是否依赖于调用的位置? 似乎很有希望,但我无法理解语法: 我想执行的示例测试用于计算VAL的依赖于Scala测试的方法只执行一次,scala,mockito,scalatest,scalamock,Scala,Mockito,Scalatest,Scalamock,我是scala新手,我正在尝试找出测试以下过程的最佳方法 我有一个类,它从构造函数参数中获取一个数字列表。该类支持列表上的各种操作,有些操作可能依赖于其他操作的输出。但每个选项只应按需执行计算,最多应执行一次。不应在构造函数中进行任何计算 示例类定义。 输入列表:列表[Int] x:返回一个向量,其中包含InputList中所有元素的平方 y:返回x中所有元素的总和 z:返回y的平方根 至于类实现,我想我能够想出一个合适的解决方案,但现在我不知道如何测试依赖操作树的计算只执行一次 课程实施方法1
正如您所提到的,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)
}
}
好的,让我们把“预成熟优化”的争论留到下次讨论 mock用于存根/验证与代码的依赖关系(也称为其他类)的交互,而不是检查代码的内部,所以为了实现您想要的,您需要这样的东西
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新手,所以我不想对隐式太疯狂,所以这是一个非常基本的示例,其中原始Operations类的API是相同的,但它将繁重的工作提取给了第三方,可以模仿,这样你就可以验证交互了。我试着退一步,您想要测试这些方法相互调用的次数的原因是什么?假设您必须处理一个非常大的列表,并且如果您调用了更高级别的函数,您的代码应该能够重用您所做的任何计算。例如,如果先调用z,然后调用x,则代码不应进行任何重新计算。我希望我的测试用例总是检查这种行为以保证这种性能。谢谢你的建议,这看起来像我需要的,但我不知道smarkMock是从哪里来的。还有一个有点相切的问题。我在几个例子中注意到了语法mock[className],但是当类的默认构造函数需要参数时会发生什么呢?当minObject.getAttributesearch.thenReturnsomeObject确保您接受问题的答案时,您也会模拟它。这正是我要找的,但我似乎无法得到sum*,被调用,一次去上班。我尝试使用Mockito.spy,但当我使用星号或尝试调用wasCalled时,它无法解决符号错误。这些方法来自不同的包吗?是的,mockito scala将您完全从mockito core抽象出来,因此您不应该使用org.mockito.mockito对象或任何其他core/java类,mockito scala中的模式类似于scalatest,您混合了所需的特性我已经更新了示例来说明这一点。如果解决了问题,请考虑将答案标记为正确答案:
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.
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)
}
}
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
}
}
}