代码之家  ›  专栏  ›  技术社区  ›  Arne Deutsch

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

  •  23
  • Arne Deutsch  · 技术社区  · 16 年前

    模仿掉依赖项对我来说似乎非常困难(我必须模仿整个“javax.lang.model.element”包)。有人成功地为注释处理器(Java6)编写了单元测试吗?如果不是。。。你的方法是什么?

    6 回复  |  直到 14 年前
        1
  •  54
  •   stevevls    12 年前

    这是一个老问题,但是注释处理器测试的状态似乎没有得到任何改善,所以我们发布了 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);
    

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

        2
  •  9
  •   Flavio    13 年前

    您是对的,模拟注释处理API(使用类似easymock的模拟库)是痛苦的。我尝试了这种方法,但很快就失败了。您必须设置许多方法调用期望。这些测试变得无法维护。

    A. 对我来说相当不错。我必须执行该程序的各个部分 javax.lang.model.* API I needed for my tests

    这是启动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);
    

    你可以看一看 source code of the Quickcheck @Samples and @Iterables source code generator tests

    维尔·格尔克!

        3
  •  2
  •   Lukas Eder    7 年前

    jOOR是一个小型Java反射库,它还提供了对内存中Java编译API的简化访问 javax.tool.JavaCompiler . 我们在单元测试中添加了对此的支持 jOOQ's annotation processors . 您可以很容易地编写如下单元测试:

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

    The above example has been taken from this blog post

        4
  •  1
  •   Helios    9 年前

    我也遇到过类似的情况,所以我创建了 Avatar

        5
  •  0
  •   Jesse Glick    14 年前

    我用过 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关于模拟整个JSR269环境的建议将导致一个纯粹的单元测试。相反,您可能希望编写更多的集成测试来检查您的处理器在javac中的实际运行情况,以确保它是正确的,但只希望避免磁盘文件。这样做需要您编写一个模拟 JavaFileManager ,不幸的是,这并不像看上去那么容易,我手头上也没有例子,但你不应该嘲笑其他事情,比如 Element 接口。

        6
  •  0
  •   Neeme Praks Tim Pote    6 年前

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

        7
  •  0
  •   Matthias Ngeo    5 年前

    Elementary ,一套JUnit5扩展,为单元测试提供了一个真正的注释处理环境。

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

    下面的代码片段演示了如何测试 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 .

    The Problem with Annotation Processors 这详细说明了单元测试注释处理器的问题。