読者です 読者をやめる 読者になる 読者になる

量産型エンジニアの憂鬱

きっと僕は何物にもなれない。

アノテーションプロセッサで生成したコードをCompile Testingを使ってテストする

Java テスト

アノテーションプロセッサを使って、SeasarプロジェクトのGen-Namesのようなものを作りました。

github.com

Gen-Namesなどは@Entityが対象ですが、@GenerateNamesアノテーションをクラスをつけるとそのクラスと親クラスのフィールド名をStringで返すようにしました。

アノテーションプロセッサはJavaPoetを使って簡単にできます。
こちらの記事がめちゃめちゃ参考になりました。

JavaFileからソースコードを生成したい場合はJavaFileObject#openWriter()を使ってWriterを取得してやればよい。

アノテーションプロセッサの

javaFile.writeTo(System.out);

を以下のようにする。

try {
    JavaFileObject sourceFile = processingEnv.getFiler().createSourceFile(クラスのFQDN);
    Writer writer = sourceFile.openWriter();
    javaFile.writeTo(writer);
    writer.close();
} catch (IOException e){
    throw new RuntimeException(e);
}

単体テストなどで実行した場合、InMemoryJavaFileObjectとして、メモリ上に作られるみたいです。生成されたファイルが見たい!って場合は、javaFile.writeTo()の引数をFileインスタンスにするか、System.outにして確認しましょう。

生成されたコードの単体テストをどうするか。

日本語やと、アノテーションプロセッサのテストはこちらもSeasarプロジェクトのAptina Unitが検索の上位に入ってきますが、近い将来EOLですし。
以下、Compile Testingを使った単体テストをしてみたときのメモ。

依存関係

mavenの場合

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>com.google.testing.compile</groupId>
    <artifactId>compile-testing</artifactId>
    <version>0.8</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>com.google.truth</groupId>
    <artifactId>truth</artifactId>
    <version>0.28</version>
    <scope>test</scope>
</dependency>

resourcesディレクトリ

resourcesディレクトリにアノテーションプロセッサ処理対象と、生成されるソースを作成しておく。
今回はHogeクラスを対象に、HogeNamesクラスが生成されることを想定。

f:id:duck8823:20160507085433p:plain


Hoge.java(処理対象)

package net.duck8823;

@GenerateNames
public class Hoge {

    private String hoge;

}



HogeNames.javaアノテーションプロセッサに生成してほしいソース)

package net.duck8823;

import java.lang.String;
import javax.annotation.Generated;

/**
 * Generated by generate-names.
 * @see https://github.com/duck8823/generate-names
 */
@Generated("net.duck8823.GenerateNamesProcessor")
public class HogeNames {
    /**
    * hogeのフィールド名を取得します.
    * @return hogeのフィールド名
    */
    public final String hoge() {
        return "hoge";
    }
}

テストクラス

import com.google.common.io.Resources;
import com.google.common.truth.Truth;
import com.google.testing.compile.JavaFileObjects;
import com.google.testing.compile.JavaSourceSubjectFactory;
import org.junit.Test;

public class ProcessorTest {
    @Test
    public void test() {
        Truth.assert_().about(JavaSourceSubjectFactory.javaSource())
                .that(JavaFileObjects.forResource(Resources.getResource("Hoge.java")))
                .processedWith(new GenerateNamesProcessor())
                .compilesWithoutError()
                .and()
                .generatesSources(JavaFileObjects.forResource(Resources.getResource("HogeNames.java")));
    }
}

コードの生成

生成されたソースの検証

  • コードの生成の記述の後にand()でつなぐ。
  • generateSources()の引数にJavaFileObjects.forResource(Resources.getResource(生成してほしいソース)))で指定する。このファイルの中身が空やとNullPointerExceptionが吐かれる。

生成してほしいソースもリソースとして置いておくことで、変更が楽になるのでよいですね。