使用方法调用替换字段中的java方法调用

时间:2015-01-26 11:54:25

标签: java bytecode instrumentation java-bytecode-asm javassist

我正在尝试在java中构建一个适合项目特定需求的模拟框架。

情景是,我有一个方法

 public String returnRandom(){

    String randomString = this.randomGenerator.returnRandom()

    }

randomGenerator是此类的依赖项,仅在运行时注入到对象中。意味着如果在没有依赖注入框架的情况下创建对象,它将为null。

在隔离测试期间,我想要对语句

进行替换分配
this.randomGenerator.returnRandom();

使用返回杂散随机值的方法,比如“Helloworld”。

我试图使用javassist.expr.FieldAccess,使用它我可以将字段替换为no操作,并且可以使用javassist.expr.MethodCall修改方法调用。

我无法弄清楚如何用虚拟或无操作替换字段。这可能是使用java协助还是我应该像asm一样进行更低级别的字节码操作?

注意: 我可以使用javassist.expr.MethodCall替换不是源于字段的方法调用。例如,如果上面的例子是

public String returnRandom(){

    String randomString = returnRandom();

    }

我可以替换为

public String returnRandom(){

    String randomString = MockedString.getSampleRandom();

    }

3 个答案:

答案 0 :(得分:3)

您可以做的最好的事情是为随机生成器创建一个接口,例如:

public interface RandomGenerator {
  public String returnRandom();
}

然后您的原始随机生成器可以实现此接口,并且使用随机生成器的类可以依赖于具有RandomGenerator接口的类。

一旦你有这个,测试是相当简单的。您创建了一个模拟生成器,可以执行您想要的操作:

public class MockRndGenerator implements RandomGenerator {
  public String returnRandom() {
    return "Helloworld";
  }
}

当你进行测试时,你会注入这个类而不是原始类。

public class Demo {
  public Demo (RandomGenerator rndGenerator) {
    this.randomGenerator = rndGenerator;
  }
  public String returnRandom(){
    String randomString = this.randomGenerator.returnRandom()
  }
}

*更新*

由于我无法在评论中添加代码,因此这是Mockito解决方案:

  

你可以随时使用Mockito来避免创建实体模拟,然后你就可以在路上设置期望和检查

class Test {
  public static rndTest() {
    RandomGenerator rnd = Mockito.mock(RandomGenerator.class);
    Mockito.when(rnd.returnRandom()).thenReturn("Helloworld");
    Demo = new Demo(rnd);
  }
}

答案 1 :(得分:1)

我可以使用javassist.expr.MethodCall解决问题。下面是用于检查可行性的表达式编辑器类。

这将使用用于获取模拟对象的代码替换targetMethod调用( methodcalltoReplace )。

new ExprEditor() {
        @Override
        public void edit(MethodCall m) throws CannotCompileException {
            try {
                if (m.where().getName().equals(sourceMethod)) {
                    if (m.getMethod().getName().equals(methodcalltoReplace)) {
                        if(lineNumberOfMethodcalltoReplace == m.getLineNumber()){
                            // The content of the hardcoded string can be replaced with runtime data
                            m.replace("$_ = ($r)"+"new com.nuwaza.aqua.sample.SampleForMethodInvocationFieldAccess().helloworld();");
                        }
                    }
                }
            } catch (NotFoundException e) {
                e.printStackTrace();
            }
            super.edit(m);
        }

有关详细文档,请参阅, Javaassist tutorial, introspection and customization

答案 2 :(得分:0)

我过去采用的一种方法是创建一个后门静态方法,用一个模拟版本替换你的随机生成器。然后,您可以使用mockito来指示应返回的内容。通过使它静止,你可以避免创建它们中的许多,只需要为整个类创建一个。像其他任何东西一样,它有利有弊,但它可能符合您的特定需求。

您可以使用方法代替getRandGen(),而不是直接调用字段,就像您在示例中一样。该方法将使用本地随机生成器或模拟静态生成器(如果已设置)。

基本上,这是一个后门,用你在单元测试期间在运行时选择的对象来覆盖你的依赖框架所选对象,它是静态的,因此它影响整个类而不仅仅是特定的实例化。