Question

Je teste des processeurs d'annotation java. Je suis capable d’écrire des tests d’intégration à l’aide de & "; JavaCompiler &"; (En fait, j'utilise & "; hickory &" pour le moment). Je peux exécuter le processus de compilation et analyser le résultat. Le problème: un seul test dure environ une demi-seconde, même sans code dans mon processeur d'annotation. C’est beaucoup trop long pour l’utiliser en style TDD.

Se moquer des dépendances me semble très difficile (je devrais me moquer de tout le package & "javax.lang.model.element &";). Quelqu'un a-t-il réussi à écrire des tests unitaires pour un processeur d'annotation (Java 6)? Si non ... quelle serait votre approche?

Était-ce utile?

La solution

Vous avez raison de vous moquer de l'API de traitement des annotations (avec une bibliothèque fictive comme easymock), c'est pénible. J'ai essayé cette approche et elle s'est effondrée assez rapidement. Vous devez configurer de nombreuses attentes en matière d’appel de méthode. Les tests deviennent impossibles à maintenir.

Une méthode de test basée sur un état a fonctionné assez bien pour moi. Je devais mettre en œuvre les éléments de javax.lang.model. * API dont j'avais besoin pour mes tests . (C’était seulement & Lt; 350 lignes de code.)

C’est la partie d’un test qui initie les objets javax.lang.model. Après l’installation, le modèle doit être dans le même état que l’implémentation du compilateur Java.

DeclaredType typeArgument = declaredType(classElement("returnTypeName"));
DeclaredType validReturnType = declaredType(interfaceElement(GENERATOR_TYPE_NAME), typeArgument);
TypeParameterElement typeParameter = typeParameterElement();
ExecutableElement methodExecutableElement = Model.methodExecutableElement(name, validReturnType, typeParameter);

Les méthodes de fabrique statique sont définies dans la classe Model implémentant les classes javax.lang.model. *. Par exemple declaredType. (Toutes les opérations non prises en charge lèveront des exceptions.)

public static DeclaredType declaredType(final Element element, final TypeMirror... argumentTypes) {
    return new DeclaredType(){
        @Override public Element asElement() {
            return element;
        }
        @Override public List<? extends TypeMirror> getTypeArguments() {
            return Arrays.asList(argumentTypes);
        }
        @Override public String toString() {
            return format("DeclareTypeModel[element=%s, argumentTypes=%s]",
                    element, Arrays.toString(argumentTypes));
        }
        @Override public <R, P> R accept(TypeVisitor<R, P> v, P p) {
            return v.visitDeclared(this, p);
        }
        @Override public boolean equals(Object obj) { throw new UnsupportedOperationException(); }
        @Override public int hashCode() { throw new UnsupportedOperationException(); }

        @Override public TypeKind getKind() { throw new UnsupportedOperationException(); }
        @Override public TypeMirror getEnclosingType() { throw new UnsupportedOperationException(); }
    };
}

Le reste du test vérifie le comportement de la classe sous test.

Method actual = new Method(environment(), methodExecutableElement);
Method expected = new Method(..);
assertEquals(expected, actual);

Vous pouvez consulter le code source des tests Quickcheck @Samples et @Iterables du générateur de code source . (Le code n'est pas encore optimal. La classe Method comporte de nombreux paramètres et la classe Parameter n'est pas testée dans son propre test, mais dans le cadre du test Method. Elle devrait néanmoins illustrer l'approche.)

Viel Gl & # 252; ck!

Autres conseils

C'est une vieille question, mais il semble que l'état des tests du processeur d'annotation ne soit pas devenu meilleur. Nous avons donc publié Compiler Test aujourd'hui. Les meilleurs documents sont dans le package -info.java , mais l’idée générale est qu’il existe une API qui permet de tester la sortie de la compilation lorsqu’elle est exécutée avec un processeur d’annotation. Par exemple,

ASSERT.about(javaSource())
    .that(JavaFileObjects.forResource("HelloWorld.java"))
    .processedWith(new MyAnnotationProcessor())
    .compilesWithoutError()
    .and().generatesSources(JavaFileObjects.forResource("GeneratedHelloWorld.java"));

teste que le processeur génère un fichier correspondant à GeneratedHelloWorld.java (fichier de référence sur le chemin d'accès aux classes). Vous pouvez également vérifier que le processeur génère une sortie d'erreur:

JavaFileObject fileObject = JavaFileObjects.forResource("HelloWorld.java");
ASSERT.about(javaSource())
    .that(fileObject)
    .processedWith(new NoHelloWorld())
    .failsToCompile()
    .withErrorContaining("No types named HelloWorld!").in(fileObject).onLine(23).atColumn(5);

Ceci est évidemment beaucoup plus simple que de se moquer et contrairement aux tests d'intégration classiques, toutes les sorties sont stockées en mémoire.

jOOR est une petite bibliothèque de réflexion Java qui fournit également un accès simplifié à l'API de compilation Java en mémoire dans javax.tool.JavaCompiler. Nous avons ajouté la prise en charge de cette fonctionnalité au test unitaire Processeurs d'annotation jOOQ . Vous pouvez facilement écrire des tests unitaires comme ceci:

@Test
public void testCompileWithAnnotationProcessors() {
    AProcessor p = new AProcessor();

    try {
        Reflect.compile(
            "org.joor.test.FailAnnotationProcessing",
            "package org.joor.test; " +
            "@A " +
            "public class FailAnnotationProcessing { " +
            "}",
            new CompileOptions().processors(p)
        ).create().get();
        Assert.fail();
    }
    catch (ReflectException expected) {
        assertFalse(p.processed);
    }
}

L'exemple ci-dessus est tiré de cet article de blog

.

Une option consiste à regrouper tous les tests dans une classe. Une demi-seconde pour la compilation, etc., est une constante pour un ensemble de tests donné, le temps de test réel d’un test est donc négligeable, je suppose.

J'ai utilisé http://hg.netbeans.org/core-main/raw-file/default/openide.util.lookup/test/unit/src/org/openide/util/test /AnnotationProcessorTestUtils.java , même si cela est basé sur java.io.File pour des raisons de simplicité et pour la surcharge de performances pour laquelle vous vous plaignez.

La suggestion de Thomas de se moquer de tout l’environnement de la JSR 269 conduirait à un pur test unitaire. Vous voudrez peut-être plutôt écrire davantage d'un test d'intégration qui vérifie le fonctionnement réel de votre processeur dans javac, en vous assurant qu'il est correct, mais vous souhaitez simplement éviter les fichiers sur disque. Pour ce faire, vous devrez écrire une maquette JavaFileManager, ce qui n’est malheureusement pas aussi simple qu’il semble et je n’ai pas d’exemples pratiques, mais vous ne devriez pas avoir besoin de vous moquer de choses comme les Element interfaces.

J'étais dans une situation similaire, j'ai donc créé la bibliothèque Avatar . Cela ne vous donnera pas les performances d'un test unitaire pur sans compilation, mais si vous les utilisez correctement, vous ne devriez pas rencontrer trop de problèmes de performances.

Avatar vous permet d'écrire un fichier source, de l'annoter et de le convertir en éléments dans un test unitaire. Cela vous permet d'unifier les méthodes de test et les classes qui consomment des objets Element sans appeler manuellement javac.

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