【问题标题】:Replace method call at runimte in JUnit test case using BCEL?使用 BCEL 在 JUnit 测试用例中替换 runimte 的方法调用?
【发布时间】:2014-11-07 16:15:41
【问题描述】:

我有一个类去服务器执行查询,我想做的是用一个模拟调用替换那个调用(这需要一个实时服务器在后台启动并运行)出于单元测试的目的返回一些预设响应。

我是 BCEL 的新手,我已经看过这篇文章,尽可能地对其进行了调整,但似乎无法让它为我工作:

Replacing statically referenced method in Java

这里有一些代码:

    // =======================================================

    public class JUnitByteCodeUtils {

    public static final String EVAL_QUERY_CLASS_NAME_SHORT = "ServerApi";
    public static final String EVAL_QUERY_CLASS_NAME_FULL  = "org.foo." + EVAL_QUERY_CLASS_NAME_SHORT;
    public static final String EVAL_QUERY_METHOD_NAME      = "evaluateQuery";
    public static final String EVAL_QUERY_METHOD_SIGNATURE = "(Ljava/lang/String;)Lorg/foo/QueryResultSet;";
    public static final Type   QUERY_RESULTSET_TYPE        = new ObjectType( QueryResultSet.class.getName() );

    /**
     * <p>Redirect/replace calls to {@code ServerApi.evaluateQuery(String)} within the specified class to the specified static 'redirectTo' method in the specified 'redirectTo' class</p>
     * 
     * @param classToRedirect  - Class containing calls to {@code ServerApi.evaluateQuery(String)}
     * @param redirectToClass  - The class containing the static method to be called instead
     * @param redirectToMethod - The static method to be called instead
     */
    public static void redirectQueryEvaluationCalls( String classToRedirect, String redirectToClass, String redirectToMethod ) {        

        JavaClass compiledClass;
        try {
          compiledClass = Repository.lookupClass( classToRedirect );
        } catch( ClassNotFoundException ex ) {
          throw new RuntimeException( "Unable to resolve class [" + classToRedirect + "]", ex );
        }

        // (2) Create a working class from the compiled class (that we can modify) 
        final ClassGen        workingClass = new ClassGen( compiledClass );
        final ConstantPoolGen constantPool = workingClass.getConstantPool();

        // (3) Locate the query evaluation method in the constant pool of the class to be modified 
        final int methodIdx = constantPool.lookupMethodref( EVAL_QUERY_CLASS_NAME_FULL, EVAL_QUERY_METHOD_NAME, EVAL_QUERY_METHOD_SIGNATURE );

        if( methodIdx > 0 ) {
          final ConstantMethodref evalQueryMethodReference = (ConstantMethodref) constantPool.getConstant( methodIdx );

          evalQueryMethodReference.setClassIndex( constantPool.lookupClass( classToRedirect ) );
          evalQueryMethodReference.setNameAndTypeIndex( constantPool.addNameAndType( "$" + EVAL_QUERY_CLASS_NAME_SHORT + "$" + EVAL_QUERY_METHOD_NAME, 
                                                                                     EVAL_QUERY_METHOD_SIGNATURE ) 
                                                      );

          // (4) Build up some new byte code instructions to redirect the existing calls to some new target method
          final InstructionList    code        = new InstructionList();
          final InstructionFactory codeFactory = new InstructionFactory( workingClass, constantPool );

          code.append( codeFactory.createInvoke( redirectToClass, 
                                                 redirectToMethod, 
                                                 QUERY_RESULTSET_TYPE, 
                                                 new Type[] { Type.STRING }, 
                                                 Constants.INVOKESTATIC ) );

          code.append( codeFactory.createReturn( QUERY_RESULTSET_TYPE ) );
          code.setPositions();

          // (5) Replace the existing query evaluation calls with calls to our redirected method
          final MethodGen methodGen = new MethodGen( Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC | Constants.ACC_STATIC,
                                                     QUERY_RESULTSET_TYPE, 
                                                     new Type[] { Type.STRING }, 
                                                     new String[] { "query" }, 
                                                     "$" + EVAL_QUERY_CLASS_NAME_SHORT + "$" + EVAL_QUERY_METHOD_NAME, 
                                                     classToRedirect,
                                                     code, 
                                                     constantPool );
          // methodGen.setMaxLocals(0);
          // methodGen.setMaxStack(1);
          // methodGen.setMaxLocals();
          // methodGen.setMaxStack();
          workingClass.addMethod( methodGen.getMethod() );

          // (6) Write out the updated class definition
          try {
              File classFile = new File( Repository.lookupClassFile( compiledClass.getClassName() ).getPath() );
              workingClass.getJavaClass().dump( classFile.getPath() );
          } catch (final IOException ex) {
              throw new RuntimeException( "Unable to save updated class [" + classToRedirect + "]", ex );
          }

        } else {
            throw new RuntimeException( "Class [" + classToRedirect.getName() + "] does not contain any query evaluation calls" );
        }   
    }
}


// =======================================================

public class QueryCaller {

    public QueryCaller() {} 

    public static String callQuery() {
        QueryResultSet result = ServerApi.evaluateQuery( "foo = bar" );

        return result.getValue();
    }
}

// =======================================================

public class TestClass {

    @Test
    public void test() throws Exception {
        JUnitByteCodeUtils.redirectQueryEvaluationCalls( "org.foo.RelevanceCaller", 
                                                         "org.foo.MockServerApi", 
                                                         "evaluateQuery" );

        System.out.println( QueryCaller.callQuery() );
    }
}

在这里,在我的单元测试开始时,我试图替换对

的调用

ServerApi.evaluateQuery(String)

查询调用者

类调用

MockServerApi.evaluateQuery(String)

这两个 evaluateQuery() 方法都返回一个 QueryResultSet 类型的对象。

但是,当我运行它时(为了在此处发布它,我不得不稍微修改此代码)我得到一个堆栈 underFlow:

java.lang.VerifyError: JVMVRFY036 堆栈下溢; class=org/foo/QueryCaller, method=$ServerApi$evaluateQuery(Ljava/lang/String;)Lorg/foo/QueryResultSet;, pc=0 at java.lang.J9VMInternals.verifyImpl(Native Method) at java.lang.J9VMInternals.verify(J9VMInternals.java:93) 在 java.lang.J9VMInternals.initialize(J9VMInternals.java:170) 在 org.foo.TestClass.test(TestClass.java:110) 在 sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 在 sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:88) 在 sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:55) 在 java.lang.reflect.Method.invoke(Method.java:613) 在 org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47) 在 org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) 在 org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44) 在 org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) 在 org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26) 在 org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:27) 在 org.junit.rules.TestWatcher$1.evaluate(TestWatcher.java:55) 在 org.junit.rules.RunRules.evaluate(RunRules.java:20) 在 org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271) 在 org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70) 在 org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50) 在 org.junit.runners.ParentRunner$3.run(ParentRunner.java:238) 在 org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63) 在 org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236) 在 org.junit.runners.ParentRunner.access$000(ParentRunner.java:53) 在 org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229) 在 org.junit.runners.ParentRunner.run(ParentRunner.java:309) 在 org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50) 在 org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38) 在 org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467) 在 org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683) 在 org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390) 在 org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)

有什么想法吗?

-GY

【问题讨论】:

标签: java junit bcel


【解决方案1】:

所以,看起来 PowerMockito 是在这里使用的:

@RunWith( PowerMockRunner.class )
@PrepareForTest( ServerApi.class )
public class TestClass {

    @Test
    public void test() throws Exception {

        // mock up
        PowerMockito.mockStatic( ServerApi.class );

        BDDMockito.given( ServerApi.evaluateQuery( "foo = bar" ) )
                  .willReturn( MockServerApi.evaluateQuery( "foo = bar" ) );

        // run code under test
        assertEquals( "The mocked value was not returned", 
                      "mocked value", 
                      QueryCaller.callQuery() );

    }
}

【讨论】:

    【解决方案2】:

    从上面的帖子中发现了 JMockit,在我看来,它使用起来非常简单和直观:

    public class TestClass {
    
        @Test
        public void test( @Mocked ServerApi serverApi ) throws Exception {
    
            // Mock up any calls you expect to happen during the test
            new Expectations() {{
              ServerApi.evaluateQuery( "foo = bar" ); result = "mocked value";
            }};
    
            // run code under test
            QueryCaller.callQuery();
    
            // Verify what actually happened
            new Verifications() {{
                ServerApi.evaluateQuery( "foo = bar" ); times = 1; // verify only called once
            }};
    
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-10-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-05-29
      相关资源
      最近更新 更多