30

私はJavaアノテーションプロセッサを試しています。「JavaCompiler」を使用して統合テストを作成できます(実際、現在は「hickory」を使用しています)。コンパイルプロセスを実行して、出力を分析できます。問題:注釈プロセッサにコードがなくても、1つのテストが約0.5秒間実行されます。これは、TDDスタイルで使用するには長すぎます。

依存関係をモックアウトするのは私には非常に難しいようです(「javax.lang.model.element」パッケージ全体をモックアウトする必要があります)。誰かが注釈プロセッサ(Java 6)の単体テストを書くことに成功しましたか?そうでない場合...あなたのアプローチは何でしょうか?

4

7 に答える 7

58

これは古い質問ですが、アノテーションプロセッサのテストの状態はこれ以上改善されていないようです。そのため、本日、コンパイルテストをリリースしました。最高のドキュメントは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);

これは明らかにモックよりもはるかに単純であり、通常の統合テストとは異なり、すべての出力はメモリに保存されます。

于 2013-09-04T17:39:56.653 に答える
10

アノテーション処理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テストの一部としてテストされます。それでもアプローチを説明する必要があります。)

VielGlück!

于 2009-11-28T21:13:14.563 に答える
3

jOORは小さなJavaリフレクションライブラリであり、のメモリ内JavaコンパイルAPIへの簡単なアクセスも提供しますjavax.tool.JavaCompilerjOOQのアノテーションプロセッサのユニットテストにこれのサポートを追加しました。次のような単体テストを簡単に作成できます。

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

上記の例は、このブログ投稿から抜粋したものです。

于 2018-12-14T09:05:34.163 に答える
1

私も同じような状況だったので、アバターライブラリを作成しました。コンパイルなしの純粋な単体テストのパフォーマンスは得られませんが、正しく使用すれば、パフォーマンスへの影響はあまり見られないはずです。

アバターを使用すると、ソースファイルを作成して注釈を付け、単体テストで要素に変換できます。これにより、javacを手動で呼び出すことなく、Elementオブジェクトを消費するテストメソッドとクラスを単体化できます。

于 2017-06-05T04:16:46.840 に答える
0

オプションは、すべてのテストを1つのクラスにバンドルすることです。コンパイルなどの0.5秒は、特定のテストセットの定数であり、テストの実際のテスト時間はごくわずかであると思います。

于 2009-11-28T08:40:47.520 に答える
0

http://hg.netbeans.org/core-main/raw-file/default/openide.util.lookup/test/unit/src/org/openide/util/test/AnnotationProcessorTestUtils.javaを使用しましたが、これは簡単にするためにオンjava.io.Fileにしているので、不満を言うパフォーマンスのオーバーヘッドもあります。

JSR 269環境全体をモックするというトーマスの提案は、純粋な単体テストにつながります。代わりに、プロセッサが実際にjavac内でどのように実行されるかをチェックし、それが正しいことをより確実にする統合テストをさらに作成することもできますが、単にディスクファイルを避けたいだけです。これを行うには、モックを作成する必要がありますJavaFileManager。これは、残念ながら見た目ほど簡単ではなく、便利な例はありませんが、Elementインターフェイスなどの他のものをモックする必要はありません。

于 2011-12-29T19:09:04.133 に答える
0

私はしばらく前に同じ問題に遭遇し、この質問を見つけました。他の回答はまともですが、まだまだ改善の余地があると感じました。この質問に対する他の回答に基づいて、単体テスト用の実際の注釈処理環境を提供するJUnit5拡張機能のスイートであるElementaryを作成しました。

ほとんどのライブラリは、注釈プロセッサを実行してテストします。ただし、ほとんどの注釈プロセッサは非常に複雑で、よりきめ細かいコンポーネントに分割されています。注釈プロセッサを実行して個々のコンポーネントをテストすることはできません。代わりに、これらのテストで注釈処理環境を利用できるようにします。

次のコードスニペットは、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

誰かが興味を持っているなら、私は注釈プロセッサのユニットテストの問題を詳述した記事「注釈プロセッサの問題」を書きました。

于 2021-04-24T03:22:43.810 に答える