Question

Cette question se pose tout en essayant d'écrire des scénarios de test. Foo est une classe au sein de la bibliothèque-cadre que je n'ai pas accès à la source.

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

que mes applications

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

Le cas de test unitaire ne parvient pas à initialiser comme je ne peux pas créer un objet Foo en raison d'autres dépendances. Le BarTest jette un pointeur null comme valeur est nulle.

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

Est-il possible que je peux utiliser la réflexion api pour définir le getX () à la non-finale? ou comment dois-je aller sur le test?

Était-ce utile?

La solution

vous pouvez créer une autre méthode que vous pouvez remplacer dans votre test:

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

alors, vous pouvez passer outre doGetX dans BarTest.

Autres conseils

Comme cela a été l'un des premiers résultats pour « écraser dernière méthode java » dans Google. Je pensais que je laisserais ma solution. Cette classe montre une solution simple en utilisant l'exemple de classe « Bagel » et gratuit pour utiliser la bibliothèque 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 otherwise you will get exceptions.
 */

package packagename;

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

/** 
 * A simple class to show how to use the library
 */
public class TestCt {

    /** 
     * The starting point for the application
     */
    public static void main(String[] args) {

        // in order for us to override the final method we must manipulate the class using the Javassist library.
        // we need to do this FIRST because once we initialize the class it will no longer be editable.
        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 and include the options in the output
            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();
        }

        // now that we have edited the classes with the new methods, we can create an instance and see if it worked

        // create a new instance of BagelWithOptions
        BagelWithOptions myBagel = new BagelWithOptions();

        // give it some options
        myBagel.setOptions("cheese, bacon and eggs");

        // print the description of the bagel to the console.
        // This should now use our new code when calling getDescription() which will include the options in the output.
        System.out.println("My bagel is: " + myBagel.getDescription());

        // The output should be:
        // **My bagel is: a plain bagel with cheese, bacon and eggs**
    }

    /**
     * A plain bagel class which has a final method which we want to override
     */
    public static class Bagel {

        /**
         * return a description for this bagel
         */
        public final String getDescription() {
            return "a plain bagel";
        }
    }

    /**
     * A sub class of bagel which adds some extra options for the bagel.
     */
    public static class BagelWithOptions extends Bagel {

        /**
         * A string that will contain any extra options for the bagel
         */
        String  options;

        /**
         * Initiate the bagel with no extra options
         */
        public BagelWithOptions() {
            options = "nothing else";
        }

        /**
         * Set the options for the bagel
         * @param options - a string with the new options for this bagel
         */
        public void setOptions(String options) {
            this.options = options;
        }

        /**
         * return the current options for this bagel
         */
        public String getOptions() {
            return options;
        }
    }
}

Seb est correct et juste pour vous assurer que vous obtenez une réponse à votre question, à court de faire quelque chose dans le code natif (et je suis sûr que cela ne fonctionne pas) ou de modifier le bytecode de la classe lors de l'exécution et la création la classe qui remplace la méthode à l'exécution, je ne peux pas voir un moyen de modifier la « finalness » d'une méthode. La réflexion ne vous aidera pas ici.

Si votre cas de test unitaire ne peut pas créer Foo en raison d'autres dépendances, qui pourrait être un signe que vous n'êtes pas faire votre test unitaire droite en premier lieu.

Les tests unitaires sont destinés à tester dans les mêmes circonstances, un code de production courrait, alors je vous suggère de recréer le même environnement de production à l'intérieur de vos tests. Dans le cas contraire, vos tests ne seraient pas complets.

Si la variable retournée par getX() ne final vous pouvez utiliser la technique expliquée dans Quelle est la meilleure façon de tester l'unité des méthodes privées pour modifier la valeur de la variable private par Reflection.

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

ce que je l'ai finalement été ci-dessus. il est un peu moche ... James solution est certainement beaucoup mieux que cela ...

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top