【问题标题】:Test class with implicit class using ScalaMock使用 ScalaMock 测试具有隐式类的类
【发布时间】:2020-03-06 10:59:30
【问题描述】:

假设我有一个将读取操作包装在 Try 块中的特征:


import scala.util.Try

trait ReadingProvider[T] {
  def readTable(tableName: String):Try[T]
}

也是一个提供使用 spark 读取的方法的类和一个用于从故障中恢复的方法的隐式类


import org.apache.spark.sql._
import org.apache.spark.sql.types.StructType
import scala.util.{Try, Success, Failure}

class SparkReadingProvider(spark: SparkSession) extends ReadingProvider[DataFrame] {
  override readTable: Try[DataFrame] = Try(spark.read.table(tableName))

  def createEmptyDF(schema: StructType): DataFrame =spark.createDataFrame(spark.sparkContext.emptyRDD[Row], schema)

}

  implicit class ReadingHandler(tryDF: Try[DataFrame]) {

    def recoverWithEmptyDF(schema: StructType): DataFrame = tryDF match {
        case Failure(ex) => //Log something
          createEmptyDF(schema)
        case Success(df) => //Log something
          df
      }
  }
}

现在我有一个包含阅读和一些转换的对象:

object MyObject {

  def readSomeTable(tableName): SparkReadingProvider => DataFrame = provider => {
    import provider.ReadingHandler
    provider.readTable(tableName).recoverWithEmptyDF
    }

  def transform: DataFrame => DataFrame = ???

  def mainMethod(tableName)(implicit val provider: SparkReadingProvider): DataFrame =
    readSomeTable(tableName) andThen transform apply provider

}

我想对MyObject 中的方法进行单元测试。我不想使用真实的文件或表格,因此我的目标是使用模拟。

在我的测试中,我试图模拟 SparkReadingProvider:

describe("reading") {
  it("should return empty dataframe when reading failed") {
    val provider: SparkReadingProvider = mock[SparkReadingProvider]
    val tableName: String = "no_table"
    provider.readTable _ expects tableName returning Failure(new Exception("Table does not exist"))
    MyObject.readSomeTable(tableName) shouldBe empty
    }
}

但是它失败并出现错误:

意外调用: SparkReadingProvider.ReaderHandler(Failure(java.lang.Exception: 表不存在))

预期: inAnyOrder { SparkReadingProvider.readTable(no_table) 一次(调用一次) }

实际: SparkReadingProvider.readTable(no_table) SparkReadingProvider.ReaderHandler(Failure(java.lang.Exception: 表不存在))

我的问题是:

  • 是否可以在当前设置中实现我想要的?
  • 如果没有,我应该如何重构我的代码
  • 如果我在不同的类中测试隐式类中可用的方法,那么在MyObject 中测试readSomeTablemainMethod 是否有意义?

【问题讨论】:

    标签: scala unit-testing apache-spark mocking scalamock


    【解决方案1】:

    问题在于扩展方法基本上是一种语法糖。

    我将用一个例子来解释。

    trait Foo
    implicit class FooImplicit(foo: Foo) {
      def bar: String = "bar
    }
    
    foo.bar
    

    被翻译成

    new FooImplicit(foo).bar
    

    如此嘲讽:

    Mockito.when(foo.bar).thenReturn("bad")
    

    变成:

    Mockito.when(new FooImplicit(foo).bar).thenReturn("bad")
    

    注意foo.bar 的处理方式,这就是问题所在。

    在当前的设置中是否可以实现我想要的?

    不,我认为在当前设置中不可能。

    如果没有,我应该如何重构我的代码

    实现这一点的唯一方法是使用隐式转换而不是隐式类。

    我将展示一个如何实现这一点的示例:

    trait Foo {
      def bar: String
    }
    
    object ImplicitFoo {
      object implicits {
        implicit fooToFooImplicit(foo: Foo): FooOps = new FooImplicit(foo)
        class FooImplicit(foo: Foo) {
          def bar: String = "bar"
        }
      }
    }
    

    和你的测试

    import org.scalatest.WordSpec
    import org.mockito.MockitoSugar
    
      class MySpec extends WordSpec with MockitoSugar {
        "My mock" should {
          "handle methods from implicit classes" in {
            val fooOps = mock[FooImplicit]
            implicit fooToOps(foo: Foo): FooImplicit = fooOps
            val foo = mock[Foo]
            when(foo.bar) thenReturn "bad" // works
          }
        }
      }
    

    在您的生产中,您需要获取形状为 Foo => FooImplicit 的隐式参数,因此当您从测试中调用该方法时,会提供实际的隐式模拟...

    如果我在不同的类中测试隐式中可用的方法 类,测试 readSomeTable 和 mainMethod 有意义吗 在 MyObject 内部?

    我认为您不需要测试 MyObject 中的 readSomeTable 和 mainMethod。但反过来也是如此。

    如果有帮助请告诉我!!

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-02-16
      • 2011-03-10
      • 2018-10-29
      • 2020-10-03
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多