用方法调用替换来自字段的 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
我正在尝试在 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