簡體   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