2009-04-14 5 views
8

Diese Frage entsteht beim Versuch, Testfälle zu schreiben. Foo ist eine Klasse innerhalb der Framework-Bibliothek, auf die ich keinen Quellzugriff habe.Java-Finalmethoden über Reflektion oder andere Mittel überschreiben?

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

meine Anwendungen

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

Das Gerät Testfall nicht in der Lage ist, zu initialisieren, da ich nicht ein Foo Objekt durch andere Abhängigkeiten schaffen. Der BarTest löst einen Nullzeiger aus, da der Wert null ist.

Gibt es eine Möglichkeit, ich kann Reflection API verwenden, um die getX() auf nicht endgültige? oder wie soll ich testen?

Antwort

5

Sie eine andere Methode erstellen können, die Sie in Ihren Test außer Kraft setzen könnte:

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

dann könnten Sie doGetX in BarTest außer Kraft setzen.

0

Wenn Ihr Unit-Testfall Foo aufgrund anderer Abhängigkeiten nicht erstellen kann, könnte dies ein Zeichen dafür sein, dass Sie Ihren Unit-Test nicht von Anfang an richtig machen.

Komponententests dienen dazu, unter den gleichen Bedingungen einen Produktionscode zu testen. Daher würde ich vorschlagen, die gleiche Produktionsumgebung in Ihren Tests wiederherzustellen. Andernfalls wären Ihre Tests nicht vollständig.

+6

Das heißt nicht, welche Unit-Tests sind für. Unit-Tests sind für extrem granulare Code-Einheiten. Was Sie beschreiben, klingt eher nach Integrationstests oder möglicherweise nach Funktionstests. –

+0

Nicht unbedingt; Ich kenne die spezifischen Umstände nicht, in denen er arbeitet (dh andere Klassen, DB usw.), aber es gibt bestimmte Tests, die Sie mit vielen Klassen gleichzeitig durchführen können - zum Beispiel, wenn Sie eine externe Bibliothek verwenden OK, um anzunehmen, dass es in Ordnung ist und alle seine Klassen in Ihren Tests verwenden. – Seb

+3

Ja, es gibt "bestimmte Tests, die Sie mit vielen Klassen gleichzeitig machen können". Das sind keine Unit-Tests. Immer noch nützlich und wichtig zu tun, aber sie sind keine ** Unit ** -Tests. –

2

Seb ist richtig, und nur um sicherzustellen, dass Sie eine Antwort auf Ihre Frage erhalten, kurz etwas in nativem Code zu tun (und ich bin mir ziemlich sicher, dass das nicht funktionieren würde) oder den Bytecode der Klasse zur Laufzeit ändern, und Beim Erstellen der Klasse, die die Methode zur Laufzeit überschreibt, kann ich keine Möglichkeit sehen, die "Endgültigkeit" einer Methode zu ändern. Die Reflexion wird dir hier nicht helfen.

+0

beziehen Sie sich auf Aspekt-orientierte aka AOP? irgendeine Idee, wenn AOP den Zugang zu Nicht-Final ändern kann? – zeroin23

+1

Ich meinte AOP nicht wirklich, ich bin mir nicht sicher, ob es das kann, ich dachte eher an die Richtung von http://jakarta.apache.org/bcel/. Allerdings, wenn etwas schwierig zu tun ist, gibt es wahrscheinlich einen Grund dafür ... und generell sollten keine harten Dinge getan werden :-) – TofuBeer

0
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); 
    ... 
    } 
} 

was ich schließlich tat, war das oben genannte. es ist ein bisschen hässlich ... James Lösung ist definitiv viel besser als das ...

10

Da dies eines der Top-Ergebnisse für "override final method java" in Google war. Ich dachte, ich würde meine Lösung verlassen. Diese Klasse ist eine einfache Lösung, die ein Beispiel Bagel-Klasse und eine kostenlose Verwendung Javassist Bibliothek verwenden:

//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; 
     } 
    } 
}