用方法调用替换来自字段的 java 方法调用

Replacing a java method invocation from a field with a method call

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

场景是,我有一个方法

 public String returnRandom(){

    String randomString = this.randomGenerator.returnRandom()

    }

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

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

的替换赋值
this.randomGenerator.returnRandom();

使用一种方法 returns 一个杂散的随机值,比如 "Helloworld"。

我正在尝试使用 javassist.expr.FieldAccess,使用它我可以将字段替换为无操作,并且可以使用 javassist.expr.MethodCall.[= 修改方法调用35=]

我不知道如何用虚拟或无操作替换该字段。这可能使用 java 辅助还是我应该像 asm 那样进行更低级的字节码操作?

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

public String returnRandom(){

    String randomString = returnRandom();

    }

我可以替换为

public String returnRandom(){

    String randomString = MockedString.getSampleRandom();

    }

你能做的最好的事情就是为随机生成器创建一个接口,比如:

public interface RandomGenerator {
  public String returnRandom();
}

那么你原来的随机生成器可以实现这个接口,而使用随机生成器的class可以依赖一个带有RandomGenerator接口的class。

有了这个之后,测试就相当简单了。您创建一个模拟生成器来执行您想要的操作:

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

当你测试时,你注入这个 class 而不是原来的。

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

* 更新 *

由于我无法在注释中添加代码,这里是 Mockito 解决方案:

you can always use Mockito to avoid creating physical mocks, and then you can set up expectations and inspections on the way

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

我过去做过的一种方法是创建一个后门静态方法,用模拟版本替换随机生成器。然后你可以使用 mockito 来指示它应该做什么 return。通过将其设置为静态,您可以避免创建许多它们,只需创建一个用于整个 class。与其他任何东西一样,它有利有弊,但它可能适合您的特定需求。

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

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

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

这会将 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