使用 void 方法模拟会导致“本地类名与流类名 'void''不兼容”,当通过 Spark 反序列化时



尝试使用 Spark 为应用程序创建测试时,我遇到以下错误:

java.io.InvalidClassException: java.lang.Void; local class name incompatible with stream class name "void"
    at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:620)
    at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1843)
    at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1713)
    at java.io.ObjectInputStream.readClass(ObjectInputStream.java:1678)
    at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1518)
    at java.io.ObjectInputStream.defaultReadFields(ObjectInputStream.java:2245)
    at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:2169)
    at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2027)

仅当我模拟某些具有void方法的类时,才会发生这种情况,这些方法将在测试单元运行期间的某个时间点调用。

例如,我的代码是:

public class MyTest {
    private MyClass uut;
    private Writer writer;
    @Captor
    private ArgumentCaptor<Dataset<Row>> rowCaptor;
    @Before
    public void setUp() {
        initMocks(this);
        writer = mock(Writer.class);
        uut = new MyClass(writer);
    }
    @Test
    public void testSomething() {
        // given
        // when
        uut.process();
        // then
        verify(writer, times(2)).write(rowCaptor.capture());
        List<Dataset<Row>> result = rowCaptor.getAllValues();
        // ...
    }
}

问题似乎在于 Mockito 序列化其内部代理类的方式。只有当您在 Spark 中运行的任务/作业实际上被序列化和反序列化时,才会产生负面影响。

org.apache.spark.scheduler.ShuffleMapTask#runTask中,任务被反序列化。Spark在这一点上基本上是:

new JavaDeserializationStream(new ByteBufferInputStream(ByteBuffer.wrap(this.taskBinary.value())), ClassLoader.getSystemClassLoader()).objIn.readObject()

这会产生确切的错误消息与

new ObjectInputStream(new ByteArrayInputStream(this.taskBinary.value())).readObject()

这将工作并正确解析对象。

特别是Java/Spark期望序列化void方法的方式与Mockito实际执行的操作之间似乎存在不匹配:"java.lang.Void"/"Void" vs. "void" .

幸运的是,Mockito允许您指定序列化其模拟的方式:

MockSettings mockSettings = Mockito.withSettings().serializable(SerializableMode.ACROSS_CLASSLOADERS);
writer = mock(Writer.class, mockSettings);

在此更改之后,测试应该可以工作。


请注意,例如verify调用很棘手/如果模拟被序列化、发送到某个地方、反序列化然后再次使用,则无法按预期工作。对模拟的调用对原始writer不可见。

相关内容

  • 没有找到相关文章

最新更新