当尝试使用 Spark 和 Scala 简化单元测试时,我使用 scala-test 和mockito-scala(以及mockito Sugar)。这只是让你做这样的事情:
val sparkSessionMock = mock[SparkSession]
然后你通常可以用“何时”和“验证”来完成所有的魔法。
但是如果你有一些需要导入的实现
import spark.implicits._
在其代码中,那么单元测试的简单性似乎消失了(或者至少我还没有找到解决这个问题的最合适的方法)。
我最终得到这个错误:
org.mockito.exceptions.verification.SmartNullPointerException:
You have a NullPointerException here:
-> at ...
because this method call was *not* stubbed correctly:
-> at scala.Option.orElse(Option.scala:289)
sparkSession.implicits();
由于输入问题,简单地模拟 SparkSession 内“隐式”对象的调用不会有帮助:
val implicitsMock = mock[SQLImplicits]
when(sparkSessionMock.implicits).thenReturn(implicitsMock)
不会让你通过,因为它说它需要你的模拟中对象的类型:
require: sparkSessionMock.implicits.type
found: implicitsMock.type
请不要告诉我我应该做 SparkSession.builder.getOrCreate()...从那时起,这不再是单元测试,而是更重量级的集成测试。
(编辑):这是一个完整的可重现示例:
import org.apache.spark.sql._
import org.mockito.Mockito.when
import org.scalatest.{ FlatSpec, Matchers }
import org.scalatestplus.mockito.MockitoSugar
case class MyData(key: String, value: String)
class ClassToTest()(implicit spark: SparkSession) {
import spark.implicits._
def read(path: String): Dataset[MyData] =
spark.read.parquet(path).as[MyData]
}
class SparkMock extends FlatSpec with Matchers with MockitoSugar {
it should "be able to mock spark.implicits" in {
implicit val sparkMock: SparkSession = mock[SparkSession]
val implicitsMock = mock[SQLImplicits]
when(sparkMock.implicits).thenReturn(implicitsMock)
val readerMock = mock[DataFrameReader]
when(sparkMock.read).thenReturn(readerMock)
val dataFrameMock = mock[DataFrame]
when(readerMock.parquet("/some/path")).thenReturn(dataFrameMock)
val dataSetMock = mock[Dataset[MyData]]
implicit val testEncoder: Encoder[MyData] = Encoders.product[MyData]
when(dataFrameMock.as[MyData]).thenReturn(dataSetMock)
new ClassToTest().read("/some/path/") shouldBe dataSetMock
}
}