繁体   English   中英

如何为 Java 注释处理器编写自动化单元测试?

[英]How to write automated unit tests for java annotation processor?

我正在试验 java 注释处理器。 我能够使用“JavaCompiler”编写集成测试(实际上我现在正在使用“hickory”)。 我可以运行编译过程并分析输出。 问题:即使我的注释处理器中没有任何代码,单个测试也会运行大约半秒。 这对于在 TDD 风格中使用它太长了。

嘲笑依赖关系对我来说似乎很难(我必须模拟整个“javax.lang.model.element”包)。 有人成功为注释处理器 (Java 6) 编写单元测试吗? 如果不是……你的方法是什么?

这是一个老问题,但似乎注解处理器测试的状态并没有好转,所以我们今天发布了编译测试 最好的文档在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);

这显然比模拟简单得多,并且与典型的集成测试不同,所有输出都存储在内存中。

你是对的,嘲笑注释处理 API(使用像 easymock 这样的模拟库)是痛苦的。 我尝试了这种方法,它很快就崩溃了。 您必须设置许多方法调用期望。 测试变得不可维护。

基于状态的测试方法对我来说相当有效。 我必须实现测试所需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);

静态工厂方法在实现 javax.lang.model.* 类的类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 测试的一部分。不过它应该说明方法。)

维尔格鲁克!

jOOR 是一个小型 Java 反射库,它还提供对javax.tool.JavaCompiler中的内存中 Java 编译 API 的简化访问。 我们添加了对此的支持以对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);
    }
}

上面的例子取自这篇博文

我也遇到了类似的情况,所以我创建了Avatar库。 它不会为您提供没有编译的纯单元测试的性能,但如果使用正确,您应该不会看到太多的性能损失。

Avatar 允许您编写源文件、对其进行注释并将其转换为单元测试中的元素。 这允许您对使用 Element 对象的方法和类进行单元测试,而无需手动调用 javac。

一种选择是将所有测试捆绑在一个类中。 编译等的半秒对于给定的测试集来说是一个常数,我假设测试的实际测试时间可以忽略不计。

不久前我遇到了同样的问题,发现了这个问题。 虽然提供的其他答案都不错,但我觉得仍有改进的空间。 基于此问题的其他答案,我创建了Elementary ,这是一套 JUnit 5 扩展,为单元测试提供真正的注释处理环境。

大多数库通过运行注释处理器来测试它们。 但是,大多数注释处理器都非常复杂,并被分解为更细粒度的组件。 通过运行注释处理器来测试单个组件是不可行的。 相反,我们使注释处理环境可用于这些测试。

以下代码片段说明了如何测试Lint组件:

import com.karuslabs.elementary.junit.Cases;
import com.karuslabs.elementary.junit.Tools;
import com.karuslabs.elementary.junit.ToolsExtension;
import com.karuslabs.elementary.junit.annotations.Case;
import com.karuslabs.elementary.junit.annotations.Introspect;
import com.karuslabs.utilitary.type.TypeMirrors;

@ExtendWith(ToolsExtension.class)
@Introspect
class ToolsExtensionExampleTest {

    Lint lint = new Lint(Tools.typeMirrors());
    
    @Test
    void lint_string_variable(Cases cases) {
        var first = cases.one("first");
        assertTrue(lint.lint(first));
    }
    
    @Test
    void lint_method_that_returns_string(Cases cases) {
        var second = cases.get(1);
        assertFalse(lint.lint(second));
    }
    
    @Case("first") String first;
    @Case String second() { return "";}
    
}

class Lint {
    
    final TypeMirrors types;
    final TypeMirror expectedType;
    
    Lint(TypeMirrors types) {
        this.types = types;
        this.expectedType = types.type(String.class);
    }
    
    public boolean lint(Element element) {
        if (!(element instanceof VariableElement)) {
            return false;
        }
        
        var variable = (VariableElement) element;
        return types.isSameType(expectedType, variable.asType());
    }
    
}

通过注释与测试类@Introspect和测试用例@Case ,我们可以在同一个文件作为测试声明测试用例。 测试用例的相应Element表示可以通过使用Cases的测试来检索。

如果有人感兴趣,我写了一篇文章, 注释处理器的问题,详细介绍了单元测试注释处理器的问题。

我使用过http://hg.netbeans.org/core-main/raw-file/default/openide.util.lookup/test/unit/src/org/openide/util/test/AnnotationProcessorTestUtils.java虽然这是基于在java.io.File为简单起见,因此有您抱怨的性能开销。

Thomas 提出的模拟整个 JSR 269 环境的建议将导致纯粹的单元测试。 相反,您可能想要编写更多的集成测试来检查您的处理器在 javac 中的实际运行方式,以提供更多的保证它是正确的,但只是想避免磁盘文件。 这样做需要您编写一个模拟JavaFileManager ,不幸的是,这并不像看起来那么容易,而且我没有手头的示例,但是您不需要模拟其他东西,例如Element接口。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM