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

时间:2019-06-13 11:38:32

标签: java apache-spark mockito

当尝试使用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();
        // ...
    }
}

1 个答案:

答案 0 :(得分:2)

问题似乎出在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"与{{1} }。

幸运的是,Mockito使您可以指定其模拟序列化的方式:

"void"

此更改后,测试应该可以正常工作。


请注意,例如MockSettings mockSettings = Mockito.withSettings().serializable(SerializableMode.ACROSS_CLASSLOADERS); writer = mock(Writer.class, mockSettings); 调用比较棘手,如果将模拟序列化,发送到某个地方,反序列化然后再次使用,则无法按预期工作。原来的verify将看不到模拟中的调用。