2009-11-28 8 views
19

Ich experimentiere mit Java Annotation Prozessoren. Ich kann Integrationstests mit dem "JavaCompiler" schreiben (tatsächlich verwende ich gerade "Hickory"). Ich kann den Kompilierprozeß laufen lassen und die Ausgabe analysieren. Das Problem: Ein einzelner Test läuft für etwa eine halbe Sekunde, auch ohne Code in meinem Annotationsprozessor. Dies ist viel zu lang, um es im TDD-Stil zu verwenden.Wie schreibt man automatisierte Komponententests für Java Annotation Processor?

Verspotten die Abhängigkeiten scheint sehr schwer für mich (ich müsste das gesamte "javax.lang.model.element" -Paket verspotten). Ist es jemandem gelungen, Komponententests für einen Annotationsprozessor (Java 6) zu schreiben? Wenn nicht ... Was wäre Ihr Ansatz?

Antwort

8

Sie haben Recht, die Annotation Processing API (mit einer Schein-Bibliothek wie Easymock) zu verspotten ist schmerzhaft. Ich habe es versucht und es ist ziemlich schnell kaputt gegangen. Sie müssen viele Methodenaufruferwartungen einrichten. Die Tests werden unwartbar.

Ein state-based Testansatz arbeitete für mich einigermaßen gut. Ich musste die Teile der javax.lang.model.* API I needed for my tests implementieren. (Das waren nur < 350 Zeilen Code.)

Dies ist der Teil eines Tests, um die Objekte javax.lang.model zu initiieren. Nach dem Setup sollte das Modell den gleichen Status wie die Java-Compiler-Implementierung haben.

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

Die statischen Factory-Methoden sind in der Klasse definiert Model die javax.lang.model implementieren. * Klassen. Zum Beispiel declaredType. (Alle nicht unterstützten Operationen Ausnahmen werfen.)

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

Der Rest des Tests überprüft das Verhalten der Klasse unter Test.

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

Sie können sich die source code of the Quickcheck @Samples and @Iterables source code generator tests ansehen. (Der Code ist noch nicht optimal. Die Methodenklasse hat viele Parameter und die Parameterklasse wird nicht in einem eigenen Test getestet, sondern als Teil des Methodentests. Sie sollte dennoch den Ansatz verdeutlichen.)

Viel Glück!

0

Eine Option besteht darin, alle Tests in einer Klasse zu bündeln. Eine halbe Sekunde zum Kompilieren usw. ist dann eine Konstante für einen gegebenen Satz von Tests, die reale Testzeit für einen Test ist beispielsweise zulässig, nehme ich an.

0

Ich habe http://hg.netbeans.org/core-main/raw-file/default/openide.util.lookup/test/unit/src/org/openide/util/test/AnnotationProcessorTestUtils.java verwendet, obwohl dies auf java.io.File basiert für Einfachheit und so hat die Leistung Overhead, die Sie beschweren.

Thomas 'Vorschlag, die gesamte Umgebung von JSR 269 zu verspotten, würde zu einem reinen Komponententest führen. Vielleicht möchten Sie stattdessen einen Integrationstest schreiben, der überprüft, wie Ihr Prozessor tatsächlich in javac läuft, um mehr Sicherheit zu geben, dass er korrekt ist, aber nur Platten-Dateien vermeiden wollen. Dies würde erfordern, dass Sie eine falsche JavaFileManager schreiben, die leider nicht so einfach ist, wie es scheint, und ich habe keine praktischen Beispiele, aber Sie sollten andere Dinge wie Element Schnittstellen nicht verspotten müssen.

35

Dies ist eine alte Frage, aber es scheint, dass der Zustand der Annotation Prozessor Tests nicht besser geworden war, so dass wir heute veröffentlicht Compile Testing. Die besten Dokumente sind in package-info.java, aber die allgemeine Idee ist, dass es eine fließende API zum Testen der Kompilierungsausgabe gibt, wenn sie mit einem Annotationsprozessor ausgeführt wird.Zum Beispiel

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

Tests, die der Prozessor eine Datei erzeugt, die GeneratedHelloWorld.java (golden-Datei auf dem Klassenpfad) übereinstimmt. Sie können auch testen, ob der Prozessor Fehlerausgang erzeugt:

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

Das ist natürlich viel einfacher, als Verspottung und im Gegensatz zu typischen Integrationstests, die gesamten Ausgabe im Speicher gespeichert wird.

0

Ich war in einer ähnlichen Situation, also habe ich die Avatar Bibliothek erstellt. Es wird Ihnen nicht die Leistung eines reinen Komponententests ohne Kompilierung geben, aber wenn Sie es richtig verwenden, sollten Sie keinen großen Leistungseinbruch sehen.

Mit Avatar können Sie eine Quelldatei schreiben, kommentieren und in einem Komponententest in Elemente umwandeln. Auf diese Weise können Sie Testmethoden und Klassen, die Element-Objekte verwenden, einbinden, ohne javac manuell aufzurufen.

Verwandte Themen