40 votes

Enregistrer les appels de méthode dans une session pour les rejouer lors de futures sessions de test?

J'ai un système de back-office qui, nous utilisons un tiers de l'API Java d'accéder à partir de nos propres applications. Je peux accéder au système en tant qu'utilisateur normal, le long de avec d'autres utilisateurs, mais je n'ai pas de pouvoirs dieu sur elle.

Donc pour simplifier les tests je voudrais lancer une session réel et d'enregistrer les appels de l'API, et de les conserver (de préférence comme modifiable code), de sorte que nous pouvons faire sécher essais plus tard, avec les appels d'API juste retourner la réponse correspondante à partir de la session d'enregistrement - et c'est l'essentiel - sans avoir besoin de parler ci-dessus mentionné système de back-office.

Donc, si mon application contient la ligne sur le formulaire:

 Object b = callBackend(a);

Je voudrais le cadre de la première capture que "callBackend()" retourné b compte tenu de l'argument, et puis quand je fais de la marche à sec à tout moment ultérieur dire "hey, donné un cet appel doit retourner b". Les valeurs de a et b sera le même (sinon, nous vous exécutez à nouveau le pas d'enregistrement).

Je peux remplacer la classe de l'API de sorte que tous les appels de méthode de capture de passer par mon code (c'est à dire de byte code de l'instrumentation pour modifier le comportement de classes en dehors de mon contrôle n'est pas nécessaire).

Quel cadre dois-je regarder pour ce faire?


EDIT: Veuillez noter que les chasseurs de primes devrait fournir des code démontrant le comportement que je recherche.

7voto

Sazzadur Rahaman Points 3040

En fait, Vous pouvez construire un tel cadre ou modèle, en utilisant le modèle de proxy. Ici j'explique comment vous pouvez le faire à l'aide de dynamic proxy modèle. L'idée est d',

  1. Écrire un gestionnaire de proxy pour obtenir de l'enregistreur et lecteur de procurations de l'API sur demande!
  2. Écrire une classe wrapper pour stocker vos informations collectées et aussi mettre en oeuvre hashCode et equals méthode de cette classe wrapper pour faciliter la recherche d' Map comme structure de données.
  3. Et enfin l'utilisation de l'enregistreur de proxy pour enregistrer et lecteur de proxy pour la relecture de but.

Comment enregistreur fonctionne:

  1. appelle la vraie API
  2. recueille les informations appel
  3. persiste données, prévue dans le contexte de persistance

Comment le lecteur fonctionne:

  1. Recueillir des informations de la méthode (nom de la méthode, les paramètres)
  2. Si les informations recueillies matchs avec les informations enregistrées puis retour recueillies précédemment valeur de retour.
  3. Si la valeur de retour ne correspond pas, persistent les informations collectées (Comme vous voulez).

Maintenant, passons à la mise en œuvre. Si votre API est - MyApi comme ci-dessous:

public interface MyApi {
    public String getMySpouse(String myName);
    public int getMyAge(String myName);
    ...
}

Maintenant, nous allons, d'enregistrement et de relecture de l'invocation de l' public String getMySpouse(String myName). Pour faire cela, nous pouvons utiliser une classe pour stocker l'invocation d'informations comme ci-dessous:

    public class RecordedInformation {
       private String methodName;
       private Object[] args;
       private Object returnValue;

        public String getMethodName() {
            return methodName;
        }

        public void setMethodName(String methodName) {
            this.methodName = methodName;
        }

        public Object[] getArgs() {
            return args;
        }

        public void setArgs(Object[] args) {
            this.args = args;
        }

        public Object getReturnValue() {
            return returnType;
        }

        public void setReturnValue(Object returnValue) {
            this.returnValue = returnValue;
        }

        @Override
        public int hashCode() {
            return super.hashCode();  //change your implementation as you like!
        }

        @Override
        public boolean equals(Object obj) {
            return super.equals(obj);    //change your implementation as you like!
        }
    }

Maintenant, Voici la partie principale, L' RecordReplyManager. Cette RecordReplyManager vous donne un objet proxy de votre API , en fonction des besoins de l'enregistrement ou de lecture.

    public class RecordReplyManager implements java.lang.reflect.InvocationHandler {

        private Object objOfApi;
        private boolean isForRecording;

        public static Object newInstance(Object obj, boolean isForRecording) {

            return java.lang.reflect.Proxy.newProxyInstance(
                    obj.getClass().getClassLoader(),
                    obj.getClass().getInterfaces(),
                    new RecordReplyManager(obj, isForRecording));
        }

        private RecordReplyManager(Object obj, boolean isForRecording) {
            this.objOfApi = obj;
            this.isForRecording = isForRecording;
        }


        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            Object result;
            if (isForRecording) {
                try {
                    System.out.println("recording...");
                    System.out.println("method name: " + method.getName());
                    System.out.print("method arguments:");
                    for (Object arg : args) {
                        System.out.print(" " + arg);
                    }
                    System.out.println();
                    result = method.invoke(objOfApi, args);
                    System.out.println("result: " + result);
                    RecordedInformation recordedInformation = new RecordedInformation();
                    recordedInformation.setMethodName(method.getName());
                    recordedInformation.setArgs(args);
                    recordedInformation.setReturnValue(result);
                    //persist your information

                } catch (InvocationTargetException e) {
                    throw e.getTargetException();
                } catch (Exception e) {
                    throw new RuntimeException("unexpected invocation exception: " +
                            e.getMessage());
                } finally {
                    // do nothing
                }
                return result;
            } else {
                try {
                    System.out.println("replying...");
                    System.out.println("method name: " + method.getName());
                    System.out.print("method arguments:");
                    for (Object arg : args) {
                        System.out.print(" " + arg);
                    }

                    RecordedInformation recordedInformation = new RecordedInformation();
                    recordedInformation.setMethodName(method.getName());
                    recordedInformation.setArgs(args);

                    //if your invocation information (this RecordedInformation) is found in the previously collected map, then return the returnValue from that RecordedInformation.
                    //if corresponding RecordedInformation does not exists then invoke the real method (like in recording step) and wrap the collected information into RecordedInformation and persist it as you like!

                } catch (InvocationTargetException e) {
                    throw e.getTargetException();
                } catch (Exception e) {
                    throw new RuntimeException("unexpected invocation exception: " +
                            e.getMessage());
                } finally {
                    // do nothing
                }
                return result;
            }
        }
    }

Si vous souhaitez enregistrer l'invocation de la méthode, tous vous avez besoin est d'obtenir une API proxy comme ci-dessous:

    MyApi realApi = new RealApi(); // using new or whatever way get your service implementation (API implementation)
    MyApi myApiWithRecorder = (MyApi) RecordReplyManager.newInstance(realApi, true); // true for recording
    myApiWithRecorder.getMySpouse("richard"); // to record getMySpouse
    myApiWithRecorder.getMyAge("parker"); // to record getMyAge
    ...

Et à relire tous vous avez besoin:

    MyApi realApi = new RealApi(); // using new or whatever way get your service implementation (API implementation)
    MyApi myApiWithReplayer = (MyApi) RecordReplyManager.newInstance(realApi, false); // false for replaying
    myApiWithReplayer.getMySpouse("richard"); // to replay getMySpouse
    myApiWithRecorder.getMyAge("parker"); // to replay getMyAge
    ...

Et Vous avez Terminé!

Edit: Les étapes de base de l'enregistreur et replayers peut être fait au-dessus mentionnés façon. Maintenant son jusqu'à vous, que la façon dont vous souhaitez utiliser ou d'effectuer ces étapes. Vous pouvez faire ce que vous voulez et ce que vous voulez dans l'enregistreur et lecteur de blocs de code et choisissez simplement votre application!

4voto

dfreeman Points 1412

Je devrais préfixe cela en disant que je partage certaines des préoccupations dans Yves Martin réponse: qu'un tel système peut s'avérer frustrant de travailler avec et, finalement, moins utile qu'il pourrait sembler à première vue.

Cela dit, à partir d'un point de vue technique, c'est un problème intéressant, et je ne pouvais pas ne pas prendre un aller à elle. J'ai mis en place un résumé de journal des appels de méthode dans un assez général. L' CallLoggingProxy classe définie, il permet l'utilisation telle que la suivante.

Calendar original = CallLoggingProxy.create(Calendar.class, Calendar.getInstance());
original.getTimeInMillis(); // 1368311282470

CallLoggingProxy.ReplayInfo replayInfo = CallLoggingProxy.getReplayInfo(original);

// Persist the replay info to disk, serialize to a DB, whatever floats your boat.
// Come back and load it up later...

Calendar replay = CallLoggingProxy.replay(Calendar.class, replayInfo);
replay.getTimeInMillis(); // 1368311282470

On pourrait imaginer d'emballage de votre objet API avec CallLoggingProxy.create avant le passage dans vos méthodes de dépistage, la saisie des données par la suite, et la persistance de l'aide quel que soit votre favori de la sérialisation système arrive à être. Plus tard, lorsque vous souhaitez exécuter vos tests, vous pouvez charger les données à sauvegarder, créer une nouvelle instance sur la base des données avec CallLoggingProxy.replay, et en passant que dans vos méthodes à la place.

L' CallLoggingProxy est écrite à l'aide de Javassist, comme Java native Proxy est limitée à travailler à l'aide d'interfaces. Cela devrait couvrir le général de cas d'utilisation, mais il y a quelques limitations à garder à l'esprit:

  • Les Classes déclarées final ne peut pas être représenté par cette méthode. (Pas facilement réparable; c'est une limitation du système)
  • L'essentiel de l'information suppose l'entrée même d'une méthode produira toujours la même sortie. (On est plus facilement réparable; l' ReplayInfo aurait besoin de garder une trace de séquences d'appels pour chaque entrée au lieu de simples paires d'entrées/sorties.)
  • L'essentiel n'est pas même à distance des threads (Assez facilement réparable; il faut juste un peu de la pensée et de l'effort)

Évidemment, l'essentiel est simplement une preuve de concept, donc c'est pas non plus été très soigneusement testé, mais je crois que le principe général est le son. Il est également possible, il ya une plus complètement cuit cadre pour atteindre ce genre d'objectif, mais si une telle chose existe, je ne suis pas au courant.

Si vous décidez de continuer avec le replay approche, alors j'espère que ce sera assez pour vous donner une orientation possible de travailler dans.

3voto

Yves Martin Points 6294

J'ai eu le même besoin de quelques mois pour les tests de non régression lors de la planification d'une lourde techniques de refactoring d'une application grand et... je n'ai rien trouvé disponible en tant que cadre.

En fait, la lecture peut être particulièrement difficile et ne peuvent travailler que dans un contexte précis - pas (ou peu) de l'application avec un niveau de complexité peut être vraiment considérés comme des apatrides. C'est un problème commun lors des tests de code de persistance avec une base de données relationnelle. Pour être pertinent, le système complet de l'état initial doit être restauré et chaque relecture étape doit avoir un impact sur l'état global de la même façon. Il devient un problème lorsque l'état du système est distribué dans des pièces comme les bases de données, des fichiers, de la mémoire... mais devinez ce qui se passe si un timestamp prises à partir d'une horloge système est utilisé quelque part !

Donc, plus la pratique de l'option est seulement pour noter... et ensuite faire un habile comparaison pour les exécutions suivantes.

En fonction du nombre de pistes vous plan, un homme piloté par session, de l'application peut être suffisant, ou que vous avez à investir dans un système automatisé de scénario dans un robot jouant avec votre interface utilisateur de l'application.

Premier à enregistrer: vous pouvez utiliser la dynamique de l'interface de proxy ou de la programmation par aspect pour intercepter l'appel de la méthode et de la capture de l'état avant et après l'invocation. Il peut signifier: dump concernés tables de base de données, copier certains fichiers, sérialiser des objets Java au format texte comme XML.

Puis comparer cette référence de capture avec une nouvelle exécution. Cette comparaison devrait être ajusté afin d'exclure toute pertinence des éléments de chaque pièce de l'état, comme les identificateurs de lignes, l'horodatage, les noms de fichiers... comparer uniquement les données où votre backend de la valeur ajoutée brille.

Enfin rien de vraiment standard, et souvent un peu de scripts et de codes peut être suffisant pour atteindre l'objectif: détecter autant d'erreurs que possible et essayer d'empêcher les non-attendus des effets secondaires.

3voto

Joop Eggen Points 30166

Cela peut être fait avec de l'AOP, la programmation orientée aspects. Il permet d'intercepter les appels de méthode par octet code de la manipulation. Faire un peu de recherche pour des exemples.

Dans un cas, cela peut faire l'enregistrement, dans l'autre la relecture.

Les pointeurs: wikipedia, AspectJ, Spring AOP.

Malheureusement, on se déplace un peu en dehors de la syntaxe java, et un exemple simple permet de mieux être recherchée ailleurs. Avec des explications.

Peut-être combiné avec des tests unitaires / certains se moquant de framework de test hors ligne des tests avec des données enregistrées.

2voto

Zagorulkin Dmitry Points 3630

vous pouvez regarder dans 'Mockito'

Exemple:

 //You can mock concrete classes, not only interfaces
LinkedList mockedList = mock(LinkedList.class);

//stubbing
when(mockedList.get(0)).thenReturn("first");
when(mockedList.get(1)).thenThrow(new RuntimeException());

//following prints "first"
System.out.println(mockedList.get(0));

//following throws runtime exception
System.out.println(mockedList.get(1));

//following prints "null" because get(999) was not stubbed
System.out.println(mockedList.get(999));
 

après que vous puissiez rejouer chaque test plus de fois et il retournera les données que vous avez insérées.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X