测试 AbstractProcessor(Java 在编译时的注解处理)

Testing AbstractProcessor (Java's annotation handling at compile time)

我正在编写一个依赖于 AbstractProcessor class 的库,因为我想编写一个很棒的库,所以我也希望有一个很好的覆盖范围。由于 预处理器 在编译时工作,我不确定如何测试该代码。

我脑子里有一些构建应该失败的测试场景。但是我该如何测试呢?我是否必须执行 gradle 并检查退出代码?是否有一种干净的方法来验证构建失败是由预期原因引起的,还是我还需要编写某种解析器?恕我直言,为了获得良好的覆盖率,这将是一笔巨大的开销。

对于那些需要示例的人:

@SupportedAnnotationTypes("eu.rekisoft.java.preprocessor.Decorator")
@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class ExamplePreprocessor extends AbstractProcessor {

    public ExamplePreprocessor() {
        super();
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        for(Element elem : roundEnv.getElementsAnnotatedWith(Decorator.class)) {
            Method method = Method.from((ExecutableElement)elem);
            if(!method.matchesTypes(String.class, StringBuilder.class)) {
                processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "No! " + elem + " has the wrong args!");
            } else {
                processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "Yey " + elem + " this is fine!");
            }
        }
        return true; // no further processing of this annotation type
    }
}

这里是一个无法编译的class:

public class AnnotationedExample {
    @Decorator
    public int breakBuild() {
        return -1;
    }

    @Decorator
    public String willCompile(StringBuilder sb) {
        return null;
    }
}

最后是无聊的注释:

@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.METHOD)
public @interface Decorator {
}

您也可以在 GitHub 检出项目,您只需要检出项目并执行 gradlew cJ。您可能需要修复 linux 和 mac.

脚本缺少的 x 权限

之前的团队编写编译器和注解处理器已经解决了这个问题,因此您可以重复使用他们的解决方案。

javac 团队使用了一个名为 jtreg 的工具。 例如,这里每个都有一个jtreg test that compiles a source file 6 times with different command-line arguments, indicating a different expected compiler output

Checker Framework team designed a testing framework specifically for annotation processors. Here is a test case using that framework, where special //:: comments indicate the lines where the annotation processor is expected to issue a warning or an error.

Google 有一个很好的 API 用于测试编译。 (https://github.com/google/compile-testing)。这是一个测试 .java 文件是否可以使用 junit.

处理器编译的示例
package org.coffeebag.processor;

import com.google.testing.compile.JavaFileObjects;
import com.google.testing.compile.JavaSourceSubjectFactory;
import org.junit.Test;

import java.io.File;
import java.net.MalformedURLException;

import static org.truth0.Truth.ASSERT;

public class ExampleTest {

    @Test
    public void EmptyClassCompiles() throws MalformedURLException {
        final MyProcessor processor = MyProcessor.testMode();
        File source = new File("path/to/test/file.java");
        ASSERT.about(JavaSourceSubjectFactory.javaSource())
                .that(JavaFileObjects.forResource(source.toURI().toURL()))
                .processedWith(processor)
                .compilesWithoutError();
    }
}

我们编写了一个简化的 API 来帮助访问 javax.tools.JavaCompiler API 进行内存中 Java 编译,这允许编写简单的基于 JUnit 的测试。在你的例子中,你可以写:

@Test
public void testFailAnnotationProcessing() {
    ExamplePreprocessor p = new ExamplePreprocessor();
    try {
        Reflect.compile(
            "com.example.test.FailAnnotationProcessing",
            "package com.example.test; " +
            "public class FailAnnotationProcessing { " +
            "    @com.example.Decorator " +
            "    public int breakBuild() { " +
            "        return -1; " +
            "    } " +
            "}",
            new CompileOptions().processors(p)
        ).create().get();
        Assert.fail();
    }
    catch (ReflectException expected) {}
}

@Test
public void testSucceedAnnotationProcessing() {
    ExamplePreprocessor p = new ExamplePreprocessor();
    Reflect.compile(
        "com.example.test.SucceedAnnotationProcessing",
        "package com.example.test; " +
        "public class SucceedAnnotationProcessing { " +
        "    @com.example.Decorator " +
        "    public String willCompile(StringBuilder sb) { " +
        "        return null; " +
        "    } " +
        "}",
        new CompileOptions().processors(p)
    ).create().get();
}

More examples can be seen here.