Pregunta

Estoy experimentando con procesadores de anotación Java. Puedo escribir pruebas de integración usando & Quot; JavaCompiler & Quot; (de hecho, estoy usando & "; nogal &"; 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 anotaciones. Esto es demasiado largo para usarlo en estilo TDD.

Burlarme de las dependencias me parece muy difícil (tendría que burlarme del paquete " javax.lang.model.element "). ¿Alguien ha logrado escribir pruebas unitarias para un procesador de anotaciones (Java 6)? Si no ... ¿cuál sería su enfoque?

¿Fue útil?

Solución

Tiene razón burlándose de la API de procesamiento de anotaciones (con una biblioteca simulada como easymock) es doloroso. Intenté este enfoque y se descompuso bastante rápido. Debe configurar muchas expectativas de llamadas a métodos. Las pruebas se vuelven imposibles de mantener.

Un enfoque de prueba basado en el estado me funcionó razonablemente bien. Tuve que implementar las partes de javax.lang.model. * API que necesitaba para mis pruebas . (Eso eran solo & Lt; 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 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 implementando las clases javax.lang.model. *. Por ejemplo declaredType. (Todas las operaciones no compatibles generarán 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 bajo prueba.

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

Puedes echar un vistazo a código fuente de las pruebas Quickcheck @Samples y @Iterables generador de código fuente . (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 & # 252; ¡ck!

Otros consejos

Esta es una vieja pregunta, pero parece que el estado de las pruebas del procesador de anotaciones no ha mejorado, por lo que lanzamos Compilar Pruebas hoy. Los mejores documentos están en el paquete -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 anotaciones. Por ejemplo,

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

prueba que el procesador genera un archivo que coincide con GeneratedHelloWorld.java (archivo dorado 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);

Obviamente, esto es mucho más simple que burlarse y, a diferencia de las pruebas de integración típicas, toda la salida se almacena en la memoria.

jOOR es una pequeña biblioteca de reflexión de Java que también proporciona acceso simplificado a la API de compilación de Java en memoria en javax.tool.JavaCompiler. Agregamos soporte para esto a la prueba de unidad procesadores de anotaciones de jOOQ . Puede escribir fácilmente pruebas unitarias como esta:

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

El ejemplo anterior se ha tomado de esta publicación de blog

Una opción es agrupar todas las pruebas en una clase. Medio segundo para compilar, etc. es más que una constante para un conjunto de pruebas dado, supongo que el tiempo de prueba real para una prueba es insignificante.

He usado 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 tiene la sobrecarga de rendimiento de la que se queja.

La sugerencia de Thomas de burlarse de todo el entorno JSR 269 conduciría a una prueba de unidad pura. En su lugar, es posible que desee escribir más de una prueba de integración que verifique cómo su procesador realmente se ejecuta dentro de javac, lo que le garantiza que es correcto, pero simplemente desea evitar los archivos de disco. Hacer esto requeriría que escribas un simulacro JavaFileManager, que desafortunadamente no es tan fácil como parece y no tengo ejemplos a mano, pero no deberías necesitar burlarte de otras cosas como Element interfaces.

Estaba en una situación similar, por lo que creé la biblioteca Avatar . No le dará el rendimiento de una prueba unitaria pura sin compilación, pero si se usa correctamente, no debería ver mucho impacto en el rendimiento.

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

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top