Подтвердить что ты не робот

Как написать автоматические модульные тесты для java-аннотационного процессора?

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

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

4b9b3361

Ответ 1

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

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

Это часть теста, чтобы инициировать объекты 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);

Статические методы factory определены в классе 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 не тестируется в собственном тесте, а как часть теста Method. Тем не менее он должен продемонстрировать подход.)

Viel Glück!

Ответ 2

Это старый вопрос, но, похоже, состояние тестирования процессора аннотаций не улучшилось, поэтому сегодня мы выпустили 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);

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

Ответ 3

jOOR - это небольшая библиотека отражений Java, которая также обеспечивает упрощенный доступ к API компиляции в javax.tool.JavaCompiler в 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);
    }
}

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

Ответ 4

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

Ответ 5

Я использовал 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 приведет к чистому unit test. Вместо этого вы можете написать больше теста интеграции, который проверяет, как ваш процессор фактически работает внутри javac, давая больше уверенности в правильности, но просто хочет избежать файлов на диске. Для этого вам потребуется написать mock JavaFileManager, что, к сожалению, не так просто, как кажется, и у меня нет никаких примеров, но вам не нужно издеваться над другими вещами, такими как Element интерфейсы.

Ответ 6

Я был в аналогичной ситуации, поэтому я создал библиотеку Avatar. Это не даст вам производительность чистого unit test без компиляции, но если вы используете правильно, вы не должны видеть большую часть производительности.

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