通过反射或其他方式重写java final方法?

问题描述:

尝试编写测试用例时出现此问题。 Foo是框架库中的一个类,我没有源访问权限。通过反射或其他方式重写java final方法?

public class Foo{ 
    public final Object getX(){ 
    ... 
    } 
} 

我的应用程序将

public class Bar extends Foo{ 
    public int process(){ 
    Object value = getX(); 
    ... 
    } 
} 

的单元测试用例是无法initalize因为我不能创建一个Foo对象,由于其他的依赖。由于值为null,BarTest会抛出一个空指针。

public class BarTest extends TestCase{ 
    public testProcess(){ 
    Bar bar = new Bar();   
    int result = bar.process(); 
    ... 
    } 
} 

有没有一种方法可以使用反射api来设置getX()为非最终?或者我该如何去测试?

你可以创建你可以在您的测试覆盖另一种方法:

public class Bar extends Foo { 
    protected Object doGetX() { 
    return getX(); 
    } 
    public int process(){ 
    Object value = doGetX(); 
    ... 
    } 
} 

然后,你可以在BarTest覆盖doGetX。

如果你的单元测试用例由于其他依赖关系而无法创建Foo,那可能表明你并没有将你的单元测试放在首位。

单元测试是为了在相同环境下测试生产代码的运行,所以我建议在测试中重新创建相同的生产环境。否则,您的测试将不会完成。

+6

这不是单元测试的目的。单元测试用于非常精细的代码单元。你所描述的听起来更像集成测试或可能的功能测试。 – 2009-04-14 16:40:07

+0

不一定;我不知道他工作的具体情况(即其他类,数据库等),但是您可以同时对多个类进行某些测试 - 例如,如果您使用外部库,它是好吧,假设它工作正常,并在你的测试中使用它的所有类。 – Seb 2009-04-14 16:47:35

+3

是的,有“你可以同时做很多课程的某些测试”。那些不是单元测试。仍然有用,并且很重要,但它们不是**单元**测试。 – 2009-04-14 17:05:44

Seb是正确的,只是为了确保你得到了你的问题的答案,在本机代码中执行某些操作(并且我确信这样做不行),或者在运行时修改类的字节码,以及创建一个在运行时重写方法的类,我看不到一种方法来改变方法的“最终性”。反思在这里不会帮助你。

如果getX()返回的变量不是final您可以使用该技术在What’s the best way of unit testing private methods?解释了通过Reflection改变private变量的值。

public class Bar extends Foo{ 
    public int process(){ 
    Object value = getX(); 
    return process2(value); 
    } 
    public int process2(Object value){ 
    ... 
    } 
} 

public class BarTest extends TestCase{ 
    public testProcess(){ 
    Bar bar = new Bar(); 
    Mockobj mo = new Mockobj();  
    int result = bar.process2(mo); 
    ... 
    } 
} 

我最终做了什么是以上。它有点丑...詹姆斯解决方案肯定比这更好...

因为这是在谷歌“重写最终方法Java”的最高结果之一。我想我会离开我的解决方案。这个类使用示例百吉圈类和免费使用的javassist库显示了一个简单的解决方案:

//This class shows how you can override a final method of a super class using the Javassist's bytecode toolkit 
//The library can be found here: http://jboss-javassist.github.io/javassist/ 
// 
//The basic idea is that you get the super class and reset the modifiers so the modifiers of the method don't include final. 
//Then you add in a new method to the sub class which overrides the now non final method of the super class. 
// 
//The only "catch" is you have to do the class manipulation before any calls to the class happen in your code. So put the 
//manipulation as early in your code as you can. 

package packagename; 

import javassist.ClassPool; 
import javassist.CtClass; 
import javassist.CtMethod; 
import javassist.CtNewMethod; 
import javassist.Modifier; 

public class TestCt { 

    public static void main(String[] args) { 
     try 
     { 
      // get the super class 
      CtClass bagel = ClassPool.getDefault().get("packagename.TestCt$Bagel"); 

      // get the method you want to override 
      CtMethod originalMethod = bagel.getDeclaredMethod("getDescription"); 

      // set the modifier. This will remove the 'final' modifier from the method. 
      // If for whatever reason you needed more than one modifier just add them together 
      originalMethod.setModifiers(Modifier.PUBLIC); 

      // save the changes to the super class 
      bagel.toClass(); 

      // get the subclass 
      CtClass bagelsolver = ClassPool.getDefault().get("packagename.TestCt$BagelWithOptions"); 

      // create the method that will override the super class's method 
      CtMethod overrideMethod = CtNewMethod.make("public String getDescription() { return super.getDescription() + \" with \" + getOptions(); }", bagelsolver); 

      // add the new method to the sub class 
      bagelsolver.addMethod(overrideMethod); 

      // save the changes to the sub class 
      bagelsolver.toClass(); 
     } 
     catch (Exception e) 
     { 
      e.printStackTrace(); 
     } 

     BagelWithOptions myBagel = new BagelWithOptions(); 

     myBagel.setOptions("cheese, bacon and eggs"); 

     System.out.println("My bagel is: " + myBagel.getDescription()); 
    } 

    public static class Bagel { 
     public final String getDescription() { 
      return "a plain bagel"; 
     } 
    } 

    public static class BagelWithOptions extends Bagel { 
     String options; 

     public BagelWithOptions() { 
      options = "nothing else"; 
     } 

     public void setOptions(String options) { 
      this.options = options; 
     } 

     public String getOptions() { 
      return options; 
     } 
    } 
}