scala - Spark scala 模拟 spark.implicits 用于单元测试
问题描述
在尝试使用 Spark 和 Scala 简化单元测试时,我使用的是 scala-test 和 mockito-scala(以及 mockito 糖)。这只是让你做这样的事情:
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
}
}
解决方案
你不能嘲笑隐含。隐式在编译时解决,而在运行时进行模拟(运行时反射,通过 Byte Buddy进行字节码操作)。您不能在编译时导入仅在运行时模拟的隐式。您必须手动解析隐式(原则上,如果您在运行时再次启动编译器,您可以在运行时解析隐式,但这会更难1 2 3 4)。
尝试
class ClassToTest()(implicit spark: SparkSession, encoder: Encoder[MyData]) {
def read(path: String): Dataset[MyData] =
spark.read.parquet(path).as[MyData]
}
class SparkMock extends AnyFlatSpec with Matchers with MockitoSugar {
it should "be able to mock spark.implicits" in {
implicit val sparkMock: SparkSession = mock[SparkSession]
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
}
}
//[info] SparkMock:
//[info] - should be able to mock spark.implicits
//[info] Run completed in 2 seconds, 727 milliseconds.
//[info] Total number of tests run: 1
//[info] Suites: completed 1, aborted 0
//[info] Tests: succeeded 1, failed 0, canceled 0, ignored 0, pending 0
//[info] All tests passed.
请注意,"/some/path"
这两个地方应该是一样的。在您的代码片段中,两个字符串不同。
推荐阅读
- spring - Spring 批处理应用程序的 PI 测试
- python-3.x - 如何检查键是否已分配给字典?
- android - 在 Android 中使用渲染脚本计算位图的清晰度
- java - 我有 /oauth/login 的端点。我不知道它在哪个类,我该如何调试才能找到它?
- python - 自动创建多个输入并将其传递给函数
- azure - 在火花数据框中的列中重复一组字符串值
- android - 如何在 Android 中使用 Material Design 在 EditText 和 TextView 中应用 shapeAppreanace
- django - Django 聚合查询包括零计数
- ios - 如何在 swift 中创建一个 Result 类型的数组?
- html - 如何将 div 对象定位到屏幕的最左边缘?