Как написать автоматизированные модульные тесты для процессора аннотаций Java? - PullRequest
24 голосов
/ 28 ноября 2009

Я экспериментирую с процессорами аннотаций Java. Я могу написать интеграционные тесты, используя «JavaCompiler» (на самом деле я использую «гикори» в данный момент). Я могу запустить процесс компиляции и проанализировать вывод. Проблема: один тест выполняется в течение полсекунды даже без кода в моем процессоре аннотаций. Это слишком долго, чтобы использовать его в стиле TDD.

Мне кажется, что отфильтровывать зависимости очень сложно (мне пришлось бы макетировать весь пакет "javax.lang.model.element"). Кому-нибудь удалось написать модульные тесты для процессора аннотаций (Java 6)? Если нет ... каков будет ваш подход?

Ответы [ 6 ]

40 голосов
/ 04 сентября 2013

Это старый вопрос, но кажется, что состояние тестирования процессора аннотаций не улучшилось, поэтому мы выпустили Compile Testing сегодня. Лучшие документы находятся в package-info.java , но общая идея заключается в том, что существует свободный API для тестирования вывода компиляции при запуске с процессором аннотаций. Например,

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

проверяет, что процессор генерирует файл, соответствующий GeneratedHelloWorld.java (золотой файл на пути к классам). Вы также можете проверить, что процессор выдает ошибку:

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

Это, очевидно, намного проще, чем имитация, и в отличие от типичных интеграционных тестов, весь вывод хранится в памяти.

9 голосов
/ 29 ноября 2009

Вы правы, издеваться над API обработки аннотаций (с фиктивной библиотекой, такой как easymock) очень болезненно. Я попробовал этот подход, и он сломался довольно быстро. Вы должны настроить многие ожидания вызова метода. Испытания становятся неосуществимыми.

A подход к тестированию на основе состояния работал для меня достаточно хорошо. Мне пришлось реализовать части API javax.lang.model. *, Которые были необходимы для моих тестов . (Это было всего <350 строк кода.) </p>

Это часть теста для инициации объектов javax.lang.model. После настройки модель должна быть в том же состоянии, что и реализация компилятора Java.

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

Статические методы фабрики определены в классе Model, реализующем классы javax.lang.model. *. Например declaredType. (Все неподдерживаемые операции будут вызывать исключения.)

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

Остальная часть теста проверяет поведение тестируемого класса.

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

Вы можете посмотреть исходный код тестов генератора исходного кода Quickcheck @Samples и @Iterables . (Код пока не оптимален. Класс Method имеет много параметров, а класс Parameter тестируется не в своем собственном тесте, а как часть теста метода. Тем не менее, он должен иллюстрировать подход.)

Viel Glück!

2 голосов
/ 14 декабря 2018

jOOR - это небольшая библиотека отражений Java, которая также обеспечивает упрощенный доступ к API компиляции в памяти в javax.tool.JavaCompiler. Мы добавили поддержку этого для модульного тестирования процессоров аннотаций jOOQ . Вы можете легко написать модульные тесты так:

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

Приведенный выше пример взят из этого поста

0 голосов
/ 05 июня 2017

У меня была похожая ситуация, поэтому я создал библиотеку Avatar . Он не даст вам производительности чистого модульного теста без компиляции, но при правильном использовании вы не увидите значительного снижения производительности.

Аватар позволяет вам писать исходный файл, комментировать его и преобразовывать в элементы в модульном тесте. Это позволяет вам тестировать методы и классы модулей, которые используют объекты Element, без ручного вызова javac.

0 голосов
/ 29 декабря 2011

Я использовал http://hg.netbeans.org/core-main/raw-file/default/openide.util.lookup/test/unit/src/org/openide/util/test/AnnotationProcessorTestUtils.java, хотя для простоты это основано на java.io.File и поэтому имеет место снижение производительности, на которое вы жалуетесь.

Предложение Томаса насмешки над всей средой JSR 269 привело бы к чистому модульному тестированию. Вместо этого вы можете захотеть написать больше интеграционного теста, который проверяет, как на самом деле ваш процессор работает внутри javac, давая больше уверенности в том, что это правильно, но просто хочет избежать файлов на диске. Для этого потребуется написать макет JavaFileManager, который, к сожалению, не так прост, как кажется, и у меня нет примеров, пригодных для использования, но вам не нужно издеваться над другими вещами, такими как Element интерфейсы.

0 голосов
/ 28 ноября 2009

Один из вариантов - объединить все тесты в один класс. Полсекунда для компиляции и т. Д. Является константой для данного набора тестов, реальное время теста для теста, по-моему, приемлемо.

...