mockito 通过模拟Java反射调用的Hive自定义UDF的单元测试用例

frebpwbc  于 9个月前  发布在  Java
关注(0)|答案(1)|浏览(51)

我有一个需求,我需要开发Hive自定义UDF,它使用Java反射API来调用外部类。
由于我是Java反射的新手,我花了一些时间学习它,并且能够做一个基本的实现。
但是我在为这个实现编写单元测试用例时遇到了一些问题,因为我在模拟反射API时遇到了一些挑战。
下面是配置单元自定义自定义项的示例。
ReverseString.java

public class ReverseString extends GenericUDF {

    private StringObjectInspector input;
    Class<?> c = null;
    Object ob = null;

    @Override
    public ObjectInspector initialize(ObjectInspector[] arg0) throws UDFArgumentException {

        // create an ObjectInspector for the input
        ObjectInspector input = arg0[0];

        // check to make sure the input is a string
        if (!(input instanceof StringObjectInspector)) {
            throw new UDFArgumentException("input must be a string");
        }

        this.input = (StringObjectInspector) input;
        System.out.println("Success. Input formatted correctly");

        return init(arg0);
    }

    public ObjectInspector init(ObjectInspector[] arg0) throws UDFArgumentException {

        try {
            c = Class.forName("com.hive.inherit.DummyUdf");
            ob = c.getConstructor().newInstance();
            Method method = c.getMethod("print",String.class);
            String res = (String) method.invoke(ob,"TEst");
            System.out.println("RES: "+res);

        } catch (NoSuchMethodException | ClassNotFoundException | IllegalAccessException | InvocationTargetException | InstantiationException e) {
            e.printStackTrace();
        }

        return PrimitiveObjectInspectorFactory.javaStringObjectInspector;
    }

    @Override
    public Object evaluate(DeferredObject[] arg0) throws HiveException {

        if (input == null || arg0.length != 1 || arg0[0].get() == null) {
            return null;
        }
        String forwards = input.getPrimitiveJavaObject(arg0[0].get());
        System.out.println("forwards:"+forwards);

        return reverse(forwards);
    }

    public  Object reverse(String in) {
        Object res = null ;
        try {
            if (this.c != null && this.ob != null) {
                Method method = this.c.getMethod("reverse", String.class);
                res = (String) method.invoke(this.ob, in);
            }else{
                c = Class.forName("com.hive.inherit.DummyUdf");
                ob = c.getConstructor().newInstance();
                Method method = c.getMethod("reverse", String.class);
                res = method.invoke(ob, in);
            }
        } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException | ClassNotFoundException | InstantiationException e) {
            e.printStackTrace();
        }
        return res;
    }

    @Override
    public String getDisplayString(String[] strings) {
        return null;
    }
}

下面是反射调用的类。
DummyUdf.java

package com.hive.inherit;

public class DummyUdf {
    

    public DummyUdf(){
        System.out.println("DummyUdf");
    }

    public String print(String str){
        System.out.println("DummyUdf-str:"+str);
        return str;
    }

    public String reverse(String in) {

        int l = in.length();
        StringBuilder sb = new StringBuilder();

        for (int i = 0; i < l; i++) {
            sb.append(in.charAt(l - i - 1));
        }
        return sb.toString();
    }
}

我试图实现的单元测试用例,
ReverseStringTest.class

@RunWith(MockitoJUnitRunner.class)
public class ReverseStringTest {

    @Test
    public void testSimpleString() throws HiveException {

        //ReverseString r = Mockito.spy(new ReverseString());
        ReverseString r = mock(ReverseString.class);
        ObjectInspector input = PrimitiveObjectInspectorFactory.javaStringObjectInspector;
        when(r.init(Mockito.any())).thenReturn(input);
        JavaStringObjectInspector resultInspector = (JavaStringObjectInspector) r.initialize(
                new ObjectInspector[] { input });
        Text forwards = new Text("hello");
        when(r.reverse(Mockito.any())).thenReturn("olleh");
        Object result = r.evaluate(new GenericUDF.DeferredObject[] { new GenericUDF.DeferredJavaObject(forwards) });
        System.out.println(result);
        assertEquals("olleh", resultInspector.getPrimitiveJavaObject(result));
    }
}

测试用例失败,出现NullPointerException异常。
java.lang.NullPointerException at com.hive.udf.ReverseStringTest.testSimpleString(ReverseStringTest.java:34) at java.base/java.lang.reflect.Method.invoke(Method.java:566) at java.base/java.util.ArrayList.forEach(ArrayList.java:1541) at java.base/java.util.ArrayList.forEach(ArrayList.java:1541)
有人能建议如何适当地嘲笑这个吗?
先谢了。

gfttwv5a

gfttwv5a1#

经过研究,我找到了解决上述问题的有效方法。
下面是工作解决方案:

public class ReverseStringTest {

    @Test
    public void testSimpleString() throws Exception {

        ReverseString reverseString = org.mockito.Mockito.spy(ReverseString.class);

        ObjectInspector[] arg0 = new ObjectInspector[] {PrimitiveObjectInspectorFactory.javaStringObjectInspector,
                PrimitiveObjectInspectorFactory.javaStringObjectInspector} ;

        when(reverseString.init(arg0)).thenReturn(PrimitiveObjectInspectorFactory.javaStringObjectInspector);
        reverseString.initialize(arg0);

        Text text = new Text("Ans");
        GenericUDF.DeferredObject[] deferredObjects = { new GenericUDF.DeferredJavaObject(text) };
        when(reverseString.reverse("Ans")).thenReturn(testcase());

        Object result = reverseString.evaluate(deferredObjects);
        System.out.println(result);
        Assertions.assertEquals(new Text("snA"),result);

    }

    public Object testcase() {
        return new Text("snA") ;

    }

代替“mock”,我们可以使用“spy”,其中部分对象将被模仿,部分将使用真实的方法调用(如果你想考虑这一点,这也有助于提高你的代码覆盖率)。
你可以参考下面的链接来了解更多关于Mockito 'mock'和'spy'。
Mockito - @Spy vs @Mock
由于我们已经扩展了GenericUDF,并且Hive实现首先调用initialize方法,因此在调用evaluate之前正确模拟传递的参数是很重要的。

相关问题