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

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

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


答案 1

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

你嘲笑注释处理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.* 类的类中定义。例如。(所有不受支持的操作都将引发异常。ModeldeclaredType

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源代码生成器测试。(代码还不是最佳的。方法类具有许多参数,并且 Parameter 类不是在其自己的测试中进行测试,而是作为方法测试的一部分进行测试。尽管如此,它应该说明该方法。

维尔·格吕克!


推荐