2009-11-28 9 views
19

Sto sperimentando con i processori di annotazione java. Sono in grado di scrivere test di integrazione usando il "JavaCompiler" (in realtà sto usando "hickory" al momento). Posso eseguire il processo di compilazione e analizzare l'output. Il problema: un singolo test viene eseguito per circa mezzo secondo anche senza alcun codice nel mio processore di annotazione. È troppo lungo per utilizzarlo in stile TDD.Come scrivere test unitari automatizzati per il processore di annotazione java?

Mocking via le dipendenze sembra molto difficile per me (dovrei prendere in giro l'intero pacchetto "javax.lang.model.element"). Qualcuno riesce a scrivere test unitari per un processore di annotazioni (Java 6)? Se no ... quale sarebbe il tuo approccio?

risposta

8

Hai ragione a deridere l'API di elaborazione delle annotazioni (con una libreria di simulazioni come easymock) è doloroso. Ho provato questo approccio e si è rotto abbastanza rapidamente. Devi impostare su molte aspettative di chiamata di metodo. I test diventano non mantenibili.

A approccio test basato sullo stato ha funzionato per me abbastanza bene. Ho dovuto implementare le parti di javax.lang.model.* API I needed for my tests. (Che erano solo < 350 righe di codice.)

Questa è la parte di un test per avviare gli oggetti javax.lang.model. Dopo l'installazione, il modello dovrebbe trovarsi nello stesso stato dell'implementazione del compilatore Java.

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

I metodi factory statici sono definiti nella classe Model attuazione del javax.lang.model. * Classi. Ad esempio declaredType. (Tutte le operazioni non supportate saranno generare eccezioni.)

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

Il resto del test verifica il comportamento della classe in prova.

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

Si può dare un'occhiata allo source code of the Quickcheck @Samples and @Iterables source code generator tests. (Il codice non è ancora ottimale. La classe Method ha molti parametri e la classe Parameter non è testata nel suo test ma come parte del test Method. Dovrebbe comunque illustrare l'approccio.)

Viel Glück!

0

Un'opzione è di raggruppare tutti i test in un'unica classe. Mezzo secondo per la compilazione ecc. È una costante per un determinato set di test, il tempo reale di test per un test è ammissibile, presumo.

0

Ho usato http://hg.netbeans.org/core-main/raw-file/default/openide.util.lookup/test/unit/src/org/openide/util/test/AnnotationProcessorTestUtils.java anche se questo è basato su java.io.File per semplicità e così ha il sovraccarico di prestazioni di cui ti lamenti.

Il suggerimento di Thomas di deridere l'intero ambiente JSR 269 porterebbe a un puro test unitario. Potresti invece voler scrivere più di un test di integrazione che controlla come il tuo processore gira effettivamente all'interno di javac, dando più sicurezza che sia corretto, ma semplicemente vuoi evitare i file del disco. Fare questo richiederebbe di scrivere un simulato JavaFileManager, che sfortunatamente non è così facile come sembra e non ho esempi pratici, ma non dovresti aver bisogno di prendere in giro altre cose come le interfacce Element.

35

Questa è una vecchia domanda, ma sembra che lo stato del test del processore di annotazione non sia migliorato, quindi abbiamo rilasciato Compile Testing oggi. I migliori documenti sono in package-info.java, ma l'idea generale è che ci sia un'API fluente per testare l'output della compilazione quando viene eseguito con un processore di annotazione.Ad esempio,

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

test che il processore genera un file che corrisponde GeneratedHelloWorld.java (file d'oro sul percorso della classe). È inoltre possibile verificare che il processore produce un output di errore:

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

Questo è ovviamente molto più semplice di beffardo e, a differenza dei test di integrazione tipiche, tutto l'output viene memorizzato.

0

Ero in una situazione simile, quindi ho creato la libreria Avatar. Non ti darà le prestazioni di un puro test unitario senza compilation, ma se usato correttamente non dovresti vedere gran parte del successo di una performance.

L'avatar consente di scrivere un file sorgente, annotarlo e convertirlo in elementi in un test unitario. Ciò consente di unitare metodi e classi di test che consumano oggetti Element, senza invocare manualmente javac.