2009-11-28 20 views
19

Estoy experimentando con procesadores de anotación Java. Puedo escribir pruebas de integración usando el "JavaCompiler" (de hecho, estoy usando "hickory" en este momento). Puedo ejecutar el proceso de compilación y analizar el resultado. El problema: una sola prueba se ejecuta durante aproximadamente medio segundo incluso sin ningún código en mi procesador de anotación. Esto es demasiado largo para usarlo en el estilo TDD.¿Cómo escribir pruebas unitarias automatizadas para el procesador de anotación Java?

Burlarse de las dependencias me parece muy difícil (tendría que burlarme de todo el paquete "javax.lang.model.element"). ¿Alguien puede tener éxito al escribir pruebas unitarias para un procesador de anotaciones (Java 6)? Si no ... ¿cuál sería tu enfoque?

Respuesta

8

Tienes razón al burlarse de la API de procesamiento de anotaciones (con una biblioteca simulada como easymock) es doloroso. Probé este enfoque y se rompió bastante rápido. Tienes que configurar muchas expectativas de llamadas de método. Las pruebas se vuelven inmanejables.

A aproximación de prueba basada en estado me ha funcionado bastante bien. Tuve que implementar las partes del javax.lang.model.* API I needed for my tests. (Eso era solo < 350 líneas de código.)

Esta es la parte de una prueba para iniciar los objetos javax.lang.model. Después de la configuración, el modelo debe estar en el mismo estado que la implementación del compilador de Java.

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

Los métodos de fábrica estáticos se definen en la clase Model la aplicación de la javax.lang.model. * Clases. Por ejemplo, declaredType. (Todas las operaciones no admitidos lanzar excepciones.)

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

El resto de la prueba verifica el comportamiento de la clase que se está probando.

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

Puede echar un vistazo a source code of the Quickcheck @Samples and @Iterables source code generator tests. (El código aún no es óptimo. La clase Method tiene muchos parámetros y la clase Parameter no se prueba en su propia prueba sino como parte de la prueba Method. Sin embargo, debe ilustrar el enfoque).

Viel Glück!

0

Una opción es agrupar todas las pruebas en una clase. La mitad de un segundo para compilar, etc. es una constante para un conjunto dado de pruebas, el tiempo de prueba real para una prueba es indescifrable, supongo.

0

He utilizado http://hg.netbeans.org/core-main/raw-file/default/openide.util.lookup/test/unit/src/org/openide/util/test/AnnotationProcessorTestUtils.java aunque esto se basa en java.io.File por simplicidad y también lo ha hecho la sobrecarga del rendimiento.

La sugerencia de Thomas de burlarse de todo el entorno JSR 269 daría lugar a una prueba de unidad pura. En su lugar, puede querer escribir más sobre una prueba de integración que comprueba cómo se ejecuta su procesador dentro de javac, lo que le da más seguridad de que es correcto, pero simplemente desea evitar los archivos del disco. Hacer esto requeriría que escribieras un simulacro JavaFileManager, que desafortunadamente no es tan fácil como parece y no tengo ejemplos a mano, pero no deberías necesitar burlarse de otras cosas como las interfaces Element.

35

Esta es una vieja pregunta, pero parece que el estado de las pruebas del procesador de anotación no había mejorado, así que hoy lanzamos Compile Testing. Los mejores documentos están en package-info.java, pero la idea general es que hay una API fluida para probar la salida de compilación cuando se ejecuta con un procesador de anotación.Por ejemplo,

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

pruebas de que el procesador genera un archivo que coincide con GeneratedHelloWorld.java (archivo de oro en la ruta de clase). También puede probar que el procesador produce una salida de error:

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

Esto es obviamente mucho más simple que burlarse ya diferencia de las pruebas de integración típicos, toda la producción se almacena en la memoria.

0

Estaba en una situación similar, así que creé la biblioteca Avatar. No le dará la realización de una prueba de unidad pura sin compilación, pero si se usa correctamente, no debería ver mucho de un golpe de rendimiento.

Avatar le permite escribir un archivo fuente, anotarlo y convertirlo a elementos en una prueba unitaria. Esto le permite probar los métodos de prueba y las clases que consumen objetos Element, sin invocar manualmente javac.

Cuestiones relacionadas