Groovyの面白いところは、Javaのprivateフィールドに何も特別な操作なしで参照したり更新することが出来る点です。
これは、特にレガシーコードをJUnitやTestNGなどの単体テストフレームワークに組み込むときに大変便利な特性になります。
本記事ではJavaのコードをGroovyで記述したTestNGのテストケースを使ってテストしてみて、privateを操作するテストコードのごく簡単なサンプルをMavenで組んでみます。
※2013-04-27追記:本記事ではGMavenを使っています。MavenでのGroovy/Javaのビルド("Joint Compile")で、もう少し実践的な構成にしたり、GMavenでなくmaven-antrun-plugin + GroovyのAntTaskを使ったMavenビルドの例を Groovy/Maven/Examples (includes "Java Joint Compile") にまとめていますので、Maven設定の細かい調整はそちらを参照してください。
※2013-07-15追記:Maven3にて、JUnit(Java, Groovy) + TestNG(Java, Groovy) + Spock(Groovy)の5種類のテストケースを一気に実行するサンプルを用意しましたので、手っ取り早くコピペしたい場合はそちらを参照してみてください: Java/Maven3/JUnit, Spock, TestNGを同時実行する
まずは練習として、JavaのコードをGroovyで記述したTestNGのテストケースでテストするごく簡単なサンプルをMavenに組み込んでみます。
サンプルソース(gjt1)ツリー:
gjt1/
pom.xml
src/
main/
groovy/gjt1/Calc1.groovy
java/gjt1/Calc2.java
test/
groovy/gjt1/Calc1Test.groovy
java/gjt1/Calc2Test.java
ソースは短いので、GistにUPしてます。DLしたら↑のソースツリーに整えてビルドしてみて下さい。
https://gist.github.com/4054109
ポイントとなる箇所だけ簡単に解説していきます。
まずgroovyライブラリの依存性を組み込みます。今回はgroovy-1.8.8のjarをdependencyに組み込んでいます:
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<version>1.8.8</version>
</dependency>
続いて、"*.groovy"をコンパイルするよう、gmavenプラグインを組み込んでいます。providerとして1.8を選択しています。
<build>
<plugins>
<plugin>
<groupId>org.codehaus.gmaven</groupId>
<artifactId>gmaven-plugin</artifactId>
<version>1.4</version>
<configuration>
<providerSelection>1.8</providerSelection>
</configuration>
<executions>
<execution>
<goals>
<goal>generateStubs</goal>
<goal>compile</goal>
<goal>generateTestStubs</goal>
<goal>testCompile</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
ちなみに、codehausのgmavenのドキュメントでは"gmaven-runtime-(version)"をdependencyに組み込んでますが、試した所 "An API incompatibility was encountered ..." という例外が発生してしまいました。外してみたところ正常に動作したのでそのままにしてます。(同じく codehaus のドキュメントで紹介されている gmaven-archetype-basic の archetype のpom.xmlではgmaven-runtimeなんて影も形も無いので、よくわかりません・・・。)
groovyソースは、デフォルトでは以下のディレクトリに配置します。(もちろん設定で変更可能です)
src/main/groovy/**/*.groovy src/test/groovy/**/*.groovy
テスト対象自体は、コンストラクタでintを2つとりインスタンスメンバにセット、calc()で加算したのを返すだけのものです。
| Groovyで実装 | src/main/groovy/gjt1/Calc1.groovy |
| Javaで実装 | src/main/java/gjt1/Calc2.java |
テストコードでは、Javaからも、Groovyからも、両方から Calc1/Calc2 それぞれテストさせてます。
| GroovyのTestNGテストケース | src/test/groovy/gjt1/Calc1Test.groovy |
| JavaのTestNGテストケース | src/test/java/gjt1/Calc2Test.java |
自明のことですが、テストはパスします。
$ mvn clean test ... ------------------------------------------------------- T E S T S ------------------------------------------------------- Running TestSuite Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.699 sec Results : Tests run: 4, Failures: 0, Errors: 0, Skipped: 0 [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------
先ほどの練習でGroovyとJavaのテストコードの連携を確認できましたので、いよいよ、GroovyのテストコードからJavaのprivateフィールドを更新してみます。
サンプルコード(gjt2)は以下のgistにUPしています。
https://gist.github.com/4054100
サンプルコード(gjt2)のディレクトリ構成です:
gjt2/ pom.xml src/main/java/gjt2/Calc.java src/test/groovy/gjt2/CalcTest.groovy
pom.xmlは練習のgjt1のものと同じです。
テスト対象となるJavaのCalcクラスも、練習の時のものと一緒でパッケージ名とクラス名を変えただけです。
Groovyで書いたTestNGのテストコードを見てみます。calc()の中で、2つめのassertのところでJavaのCalcクラスのprivateフィールドの更新を試みています。
...
@Test
public void calc() {
Calc c = new Calc(10, 20);
Assert.assertEquals(c.calc(), 30);
// ここでJavaのCalcクラスのprivateフィールドを更新している。
c.v1 = 1;
c.v2 = 2;
// privateフィールドへの更新が反映されていることを確認
Assert.assertEquals(c.calc(), 3);
}
単体テストを実行してみます。
$ mvn test ... ------------------------------------------------------- T E S T S ------------------------------------------------------- Running TestSuite Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.828 sec Results : Tests run: 1, Failures: 0, Errors: 0, Skipped: 0 [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------
正常にテストが成功しました。
今回はごく簡単なテストケースを通じてprivateフィールドをGroovyからの更新を紹介しましたが、応用すれば、従来では不可能か非常に難易度が高いと考えられていた、Singletonが関連するような箇所のtestコードも柔軟かつ容易に構築できそうです。Groovyではこの他にもメタプログラミングを容易にしてくれる機能が揃っていますので、JavaのレガシーコードをGroovyでテストする道筋が見えてきます。
以下、参考資料です。
MavenでGroovyを使う資料:
GroovyでTestNGのテストケースを書く資料: