我正在尝试使用 java 注释处理器。我可以使用“JavaCompiler”编写集成测试(事实上我现在正在使用“hickory”)。我可以运行编译过程并分析输出。问题:即使我的注释处理器中没有任何代码,单个测试也会运行大约半秒。这对于在 TDD 样式中使用它来说太长了。
嘲笑依赖对我来说似乎很困难(我不得不嘲笑整个“javax.lang.model.element”包)。有人成功为注释处理器(Java 6)编写单元测试吗?如果不是......你的方法是什么?
我正在尝试使用 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);
静态工厂方法在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 测试的一部分。不过它应该说明该方法。)
维尔格吕克!
jOOR 是一个小型 Java 反射库,它还提供了对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);
}
}
我也遇到了类似的情况,所以我创建了Avatar库。它不会为您提供没有编译的纯单元测试的性能,但如果使用正确,您应该不会看到太多的性能损失。
Avatar 允许您编写源文件、对其进行注释并将其转换为单元测试中的元素。这允许您对使用 Element 对象的方法和类进行单元测试,而无需手动调用 javac。
一种选择是将所有测试捆绑在一个类中。对于给定的一组测试,编译等的半秒是一个常数,我假设测试的实际测试时间可以忽略不计。
我使用过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 中的实际运行情况,从而更加保证它是正确的,但只是想避免使用磁盘文件。这样做需要您编写一个 mock JavaFileManager
,不幸的是,这并不像看起来那么容易,而且我没有方便的示例,但您不需要模拟Element
接口等其他东西。
不久前我遇到了同样的问题,发现了这个问题。尽管提供的其他答案都不错,但我觉得仍有改进的余地。基于这个问题的其他答案,我创建了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
.
如果有人感兴趣,我写了一篇文章,注释处理器的问题,详细介绍了单元测试注释处理器的问题。