Spockのテストコンパイルが遅いので、GradleでGroovyの増分コンパイルを有効化する

Spockでユニットテストを書いている、Gradleを使ったJavaプロジェクトがある。

Intellijユニットテストを実行するとき、ソースコードに変更があると毎回ユニットテストのすべてのクラスがコンパイルされてしまい、実行までに2分前後のコンパイル待ちが発生していた。

Javaのようにインクリメンタルコンパイル(増分コンパイル)ができないかと思って調べたら、設定できたのでメモ。

環境

Gradle 7.1.1、OpenJDK 11にて確認。

Gradle 5.6以降、JDK 7以上で利用可能。

問題

Gradleプロジェクトにて、Spockを使ったGroovyテストクラスに1つでも変更があると、常にすべてのクラスがコンパイルされる。

また、テストクラスに変更がなくても、Javaのメインクラスに変更があった場合も、同様にすべてのクラスがコンパイルされる。

Gradleによるコンパイル時、Javaであればデフォルトでインクリメンタルコンパイル(増分コンパイル・差分コンパイル)が有効になっているため、一度コンパイルした後は、変更のあったクラスおよび変更の影響を受けるクラスだけがコンパイルされる。

Groovyの場合、デフォルトではインクリメンタルコンパイルが有効になっていないため、テストクラスに変更があった場合や、メインクラスが変更された場合、すべてのテストクラスが再コンパイルされていると思われる。

Groovyテストクラス数が多く、フルコンパイルにはおおよそ2分程度かかるため、短縮したい。

対応

インクリメンタルコンパイルをGroovyコンパイルにも適応できるが、デフォルトでは無効になっているため、有効化する。

手順としては以下の2つ。

  1. コンパイル回避の有効化
  2. インクリメンタルビルドの有効化

また、これらの変更により、テストクラスのコンパイルUP-TO-DATE と判定された場合、デフォルトではユニットテストの実行自体が行われなくなるため、Gradleの test タスクを常に実行するよう build.gradle にも変更を行う。

設定手順

Groovyコンパイル回避の有効化

インキュベーション機能のため、明示的に有効化する必要がある。

こちらの手順通り、Javaプロジェクトの settings.gradle に、以下の記述を追加。

enableFeaturePreview('GROOVY_COMPILATION_AVOIDANCE')

Groovyインクリメンタルビルドの有効化

Groovy Pluginの説明を基に、 build.gradle に設定を追加。

tasks.withType(GroovyCompile).configureEach {
  options.incremental = true
}

GroovyのコンパイルオプションであるgroovyOptionsではなく、Javaコンパイルオプションである[options]が設定の対象。

手順通り GroovyCompile タスクすべてに設定しているが、ユニットテストだけが対象であれば、 compileTestGroovy タスクに対してのみ設定すればいいと思われる。

常にユニットテストを実行するよう設定

先の2つの設定で、Groovyのインクリメンタルコンパイルは有効となるが、 compileTestJava および compileTestGroovy タスクが両方とも UP-TO-DATE になると、 test タスクも UP-TO-DATE 扱いで実行されなくなる。

これを避けるために、常に test タスクを実行するよう、 build.gradle に以下の設定を追加。

test {
  outputs.upToDateWhen { false }
}

TaskOutputs#upToDateWhenfalse を返すことで、常に test タスクを実行できる。

結果

これらの設定を行ったことで、Javaのメインクラスを変更した場合でも、メソッドの内部のみの変更であれば、Groovyテストクラスは UP-TO-DATE となり、再コンパイルが行われなくなった。

また、テストクラスの一部を変更した場合、差分に対してのみコンパイルが行われるようになったため、1クラスの変更であれば数秒で再コンパイルが完了するようになった。

振り返り

インクリメンタルコンパイルについては、インクリメンタルJavaコンパイルおよびインクリメンタルJavaコンパイルの既知の問題を参照。

有効化したことで劇的な効果があった。Gradle 7.2時点ではデフォルトで有効化されていないので、Spockなどを使っていればメリットがありそう。