システム開発で思うところ

Javaで主にシステム開発をしながら思うところをツラツラを綴る。主に自分向けのメモ。EE関連の情報が少なく自分自身がそういう情報があったら良いなぁということで他の人の参考になれば幸い

Pluggable Annotation Processing API Sample(実践編2)

前回の続き

vermeer.hatenablog.jp

今回はAnnotationProcessorを開発する際のテストのやり方です。一部、自分のライブラリの機能を使うところがありますが、それを除けば基本的に汎用的なものだと思います。

はじめに

前回の続きを想定しているので、以下のコードをクローンなりしてローカルのMavenリポジトリが作成されていることを前提としています。

Git

vermeerlab / apt-core / source / — Bitbucket

vermeerlab / apt-extend-sample / source / — Bitbucket

vermeerlab / apt-extend-sample-client / source / — Bitbucket

Maven

テストに必要なプロジェクトを追記します。
このプロジェクトがあることで任意のJavaコードをAnnotationProcessorの処理対象として指定することが出来るようになります。

<dependencies>
    <!-- for annotation processor start-->
    <dependency>
        <groupId>com.google.testing.compile</groupId>
        <artifactId>compile-testing</artifactId>
        <version>0.10</version>
        <scope>test</scope>
    </dependency>
    <!-- annotation processor end -->
</dependencies>

生成したコードの検証

テストコード

例示のサンプルは
vermeerlab / apt-extend-sample / source / — Bitbucket のものです。

f:id:vermeer-1977:20170819173222p:plain

@Test
public void 生成コードの検証() {
    Truth.assert_()
            .about(JavaSourceSubjectFactory.javaSource())
            .that(JavaFileObjects.forResource(Resources.getResource(
                    "test/Sample.java" ← 生成元になるコードのパス
            )))
            .processedWith(new AnnotationProcessorController(false)) ← 実行するAnnotationProcessor
            .compilesWithoutError()
            .and()
            .generatesSources(JavaFileObjects.forResource(Resources.getResource(
                    "test/SampleFactory.java" ← 生成されるコードの期待値と同じコードのパス
            )));
}

実行するAnnotationProcessor

テストや疎通確認時点ではAnnotationProcessorControllerの引数をtrueにしてDebugUtil#printで標準出力すれば、生成したコードの内容を確認することが出来ます。確認が終わればfalseにすれば標準出力はされなくなります。

補足

私は1つのコントローラーから任意のコマンドを実行するという方式なのでAnnotationProcessorControllerを指定しますが、一般的には生成処理毎にAnnotationProcessorを作成してサービスローダーに登録すると思います。その場合は、processedWithの引数に作成したProcessorを指定してください。

期待値となる生成コード

標準出力で生成されるコードの確認は可能ですが期待値検証にはなっていません。同値のコードを準備しておくことで継続性を担保できます。

なのですが実際には地味に手間取りました。ということで私の手順です。

  1. 期待値となるコードの「ファイルだけ」を作成する
  2. AnnotationProcessorController(true)にする
  3. テストを実行して生成コードを標準出力する(テストはエラー) f:id:vermeer-1977:20170819193958p:plain

  4. 意図通りの結果が出力されているか確認して意図通りになるまで生成ロジックを修正する。

  5. 標準出力されたコードを期待値となるコードファイルにコピー&ペーストしてコンパイルエラーが無いことを確認する。

  6. IDEではなくテキストエディタなどで「直接」コピー&ペーストして保存する

  7. テストが成功で終わることを確認する

私がはまったところ

期待値比較は完全一致

IDEで自動整形されていても内容が正しければ検証にて正常と評価されると思っていました。 テキスト比較で評価しているようで体裁も含めて完全に一致していないといけませんでした。

完全一致しているはずなのに…

はじめの一行目の空白行が足りなかったため標準出力の内容をテキストエディタでコピー&ペーストしているのにテストエラーが解消されないという事象でした。標準出力している内容をテキストエディタでコピー&ペーストしてもテストエラーになる場合は1行目に空白行を入れるといった「標準出力では気が付きにくい違い」がある可能性があります。

その他

格納場所はsrc/test/resources配下にクラスのパッケージと同じフォルダを作成して格納します。インプットとアウトプットともにパッケージ構成次第では同じ場所に格納されるので命名ルールに注意が必要です。

さいごに

こんな感じで手間はかかりましたが、以上が私の考えるAnnotationProcessorのテスト手順になります。
生成後に期待するコードを作成すると言いつつ、標準出力されている内容のコピー&ペーストというのも変な話ではありますが、そもそも生成したいコードはコード編集ロジックを組み立てる前に検討しています。そういう意味では検証結果に そのまま使用はしないけれども 期待するコード相当のものは作成しています。特にJavaPoetのようなライブラリを使ってコード編集をする場合、事前に検討もせず構築するのは結構無謀だと思います*1

追記

2017/8/19

ブログ記載時の記述に一致するブランチを作成してリンクを修正。

2017/11/20

AnnotationProcessorを使ったツールのリンク

プロパティファイルからEnumを生成 - システム開発で思うところ

*1:私自身、このくらいなら出来るだろうと思ってJavaPoetでコード生成をしようとして、「あれ?どんなクラスを作るつもりだったっけ?」と無駄に時間を使ってしまいました。