[英]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.