【发布时间】:2023-03-02 22:32:02
【问题描述】:
我有一些使用 spark 运行的 Scala 代码,但我对其进行了简化:
// Not Serializable
class Config
object FileReader extends FileReader
class FileReader extends Serializable {
def read(config: Config): String = config.getClass.toString
}
object Task extends Task(FileReader)
class Task(fileReader: FileReader) extends Serializable {
def execute(config: Config): Unit = {
fileReader.read(config)
}
}
不是Config 不是Serializable。
我想为它们写一些测试,Task 的实例需要是Serializable,因为它可能被序列化并发送给 spark worker。
我使用这个函数来检查一个对象是否可以被序列化:
def checkSerializable(obj: AnyRef, name: String) = {
println("### checking " + name + ": " + obj.getClass)
new ObjectOutputStream(new ByteArrayOutputStream()).writeObject(obj)
println(name + " ok")
}
首先我们检查一个普通的Task实例是否可以序列化:
val task = new Task(new FileReader)
checkSerializable(task, "no-mockito")
输出:
### checking no-mockito: class Task
no-mockito ok
看起来不错。
但我想用 Mockito 模拟 FileReader,所以我的代码是:
val fileReader = Mockito.mock(classOf[FileReader])
val config = Mockito.mock(classOf[Config])
Mockito.when(fileReader.read(config)).thenReturn("mocked")
val task = new Task(fileReader)
checkSerializable(task, "with-mockito1")
报错Config不可序列化:
### checking with-mockito1: class Task
java.io.NotSerializableException: Config$$EnhancerByMockitoWithCGLIB$$c7dcb0a5
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1165)
at java.io.ObjectOutputStream.writeArray(ObjectOutputStream.java:1359)
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1155)
at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1535)
at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1496)
这很奇怪,因为config 只是一些方法参数,而不是类字段!
我稍微修改了我的代码以使 config 可序列化:
val fileReader = Mockito.mock(classOf[FileReader])
val config = Mockito.mock(classOf[Config], Mockito.withSettings().serializable())
Mockito.when(fileReader.read(config)).thenReturn("mocked")
val task = new Task(fileReader)
checkSerializable(task, "with-mockito2")
它仍然失败,另一个NotSerializableException:
### checking with-mockito2: class Task
java.io.NotSerializableException: org.mockito.internal.creation.DelegatingMethod
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1165)
at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1535)
at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1496)
at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1413)
不确定为什么在使用 Mockito 时 config 实例会包含在 task 中?又该如何避免呢?
演示项目:https://github.com/freewind/mockito-serialization-issue,你可以直接克隆它并运行demo/MockitoDemo.scala
更新:
另一个值得注意的是:如果我删除了这一行
fileReader.read(config)
来自Task,这意味着Task 将是:
class Task(fileReader: FileReader) extends Serializable {
def execute(config: Config): Unit = {
// removed this line: fileReader.read(config)
}
}
不会再抛出NotSerializableException(我没有更改测试代码)
【问题讨论】:
-
您正在尝试序列化一个
Task,该Task已填充了 mock fileReader,并且此模拟 fileReader 已存储有关其读取方法为时应执行的操作的信息使用模拟配置调用。当您序列化该任务时,需要对模拟 fileReader 进行序列化,这将不起作用,因为它保留了“何时”信息(第一次失败),并且因为这些模拟无论如何都具有不可序列化的东西(第二次失败)。不确定有什么方法可以像这样混合模拟和序列化。 -
@Shadowlands,刚刚更新了我的问题,又一个奇怪的事情
-
Re: 更新 - 在这一点上,模拟似乎既不包含对
config的引用(尽管测试代码中的when行现在不应该编译,如果你没有'不要删除它),也不是它之前拥有的DelegatingMethod实体。如果您在 fileReader 中添加任何方法,则可能会返回后一个问题。 -
@Shadowlands 感觉自己的更新没说清楚,所以又更新了一次
标签: scala serialization apache-spark mockito