繁体   English   中英

如何在scala对象中模拟方法内部的方法?

[英]How to mock method inside method in scala object?

我想用一个简单的例子来解决我的问题。

我有以下特点:

trait ReaderTrait {

 def readTable(tableName:String, closingDate: String)(implicit sparkSession: SparkSession): DataFrame = {

sparkSession.read.table(
      tableName
    ).filter(col("closingDate") === closingDate)
 
 }

def read(fullTable:String, closingDate: String)(implicit sparkSession: SparkSession): DataFrame

}

我有以下对象:

object Reader extends  ReaderTrait
 {

override final def read(fullTable: String, closingDate: String)(implicit sparkSession: SparkSession): DataFrame =
 {

  readTable(fullTable: String, closingDate: String)
//.filter..
//I have business logic here which I want to test, I want to mock only readTable
  }

}

现在我只想模拟 Object 中的readTable方法。

我有一个测试类:

class ReaderTest extends FeatureSpec with MockitoSugar {

 scenario("first") {

//sample dataframe1 created ......

    val mockTrait = mock[Reader.type ] 
    when(mockTrait.readTable("abc", "sss")(spark)) thenReturn (dataframe1)
    val result = mockTrait.read("abc", "sss")(spark)
    result.show()
  }
}

运行上述测试类后,它给了我以下错误:

抛出了 java.lang.NullPointerException。

请帮我解决这种情况; 我必须在对象中的 read 方法中模拟readTable方法,而不对现有代码进行任何更改。

这里有很多问题:

  • 您不能模拟对象。 mock[Reader.type]没有意义。 不过,您可以使用mock[ReaderTrait]来模拟特征

  • 模拟不像你想象的那样工作。

如果你有class A { def foo = ""; def bar = foo } class A { def foo = ""; def bar = foo } ,然后你做

    val m = mock[A]
    when(m.foo).thenReturn("baz")
    val result = m.bar

result的值不会是“baz”,而是null 为什么? 因为m是一个mock——一个虚拟对象,所有的方法都被删除了。 m.bar调用foo ,因为它是一个存根,它只返回null

有一种方法可以只存根类的某些方法,而让其他方法保持原始行为。 如果您想了解更多信息,请查看spy的概念(您会使用spy(new A)而不是mock[A] )。 我不会在这里讨论这个,因为使用间谍不是一个好习惯 这让我想到了第三点......

  • 你不应该存根你正在测试的类的方法。 当你需要时,这是你的类设计违反单一职责原则的症状,这不是一件好事。

在这种情况下,编写测试的正确方法是将readTable “提供程序”类与您正在测试的功能隔离开来。 例如:

trait TableReader { 
  def readTable(table:String, date: String)(implicit s: SparkSession) = 
    s.read.table(table).filter(col("closingDate") === date) 
}
object TableReader extends TableReader

class Reader(val tr: TableReader) { 
   def read(table: String, date: String)(implicit s: SparkSession) = 
      tr.readTable(table, date)
}
object Reader extends Reader(TableReader)

现在您可以使用以下内容测试您的Reader类:

    val tr = mock[TableReader]
    wen(tr.readTable(any, any)(any)).thenReturn(dataframe1)
    val reader = new Reader(tr)
    reader.read("foo", "bar")(spark) shouldBe dataframe1
    verify(tr).readTable("foo", "bar")(spark)

这会起作用,但有点不清楚你在这里要测试的是什么 你没有测试readTable因为你把它存根了。 而且read没有逻辑,所以没有什么可以测试的。

我建议将过滤逻辑从TableReader移到Reader中,这样可以更清晰地分离两个类之间的职责:一个负责从表中获取数据,另一个应用过滤和其他业务逻辑:

trait TableReader { 
  def readTable(t: String)(implicit s: SparkSession) = s.read.table(t)
}
class Reader(tr: TableReader) {
  def read(table: String, date: String)(implicit s: SparkSession) = 
     tr.readTable(table).filter(col("closingDate") === date) 
}

这使您能够编写有意义的测试,例如:

  reader.read("foo", "bar")(spark)
    .select("closingDate")
    .as[String]
    .collect
    .toList shouldBe List("bar", "bar", "bar")

(基本上,检查closingDate === "bar"的过滤器是否已正确应用于数据框)

这里的想法是您不需要测试TableReader ,因为那里没有任何业务逻辑,它只是 spark 的简单包装器,因此您可以轻松地将其存根。

Reader类只关心想要测试的业务逻辑,并不知道实际数据来自哪里,所以你不需要存根它的任何方法,只需提供一个模拟提供程序数据( mock[TableReader] )。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM