uehaj's blog

Grな日々 - GroovyとかGrailsとかElmとかRustとかHaskellとかReactとかFregeとかJavaとか -

groovyでテスト! with GMaven

Groovyはテストを書くのに向いている、って言うじゃない。

んで、いまどき、開発はMavenベースじゃないですか。
つうことで、mavenのプロジェクトでGroovyテストを書いてみましょうか、という話。

まず、一番うまく行くパターンとして、Maven用の公式groovyプラグインであるところのgmavenが提供する、gmaven-archetype-basicアーキタイプをそのまま使う形。mavenなので、プロジェクトを以下のように生成すると対話形式で

$ gmaven-archetype-basic
:
Choose archetype:
:
40: internal -> gmaven-archetype-basic (Groovy basic archetype)

とでてくるので40番を選べば*1pom.xmlも出来てフォルダ構成、サンプルのコード・テストコードまでも作ってくれます。あとはsrc/test/groovyにテストコードを置くだけ。mvn testでsurefireがテストレポートを作ってくれます。

以上。まる。

・・・・で済むなら何も苦労はないのですが、なんらかの理由で

  • 開発対象コード(の一部)がgroovyでありかつgroovycコンパイルしたくない・できない理由がある
  • テストコードをgroovycコンパイルしたくない・できない。

のいずれかもしくは両方のとき*2、はまることになります。なぜならsurefireによるテストコード実行はテストコード自身およびテスト対象コードがコンパイルされたクラスファイルであることを想定しているようだからです。少なくとも、

      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <configuration>
          <additionalClasspathElements>
            <additionalClasspathElement>src/main/groovy</additionalClasspathElement>
          </additionalClasspathElements>
        </configuration>
      </plugin>

としてみてうまく動作しませんでした。

しょうがないのでなんとか対応する方法としては

などが考えられます。(案1)の問題点の最初の1つは、gmavenのスクリプト実行機能はまず、スクリプトを実行できても、GroovyTestCaseやJUnitのサブクラスであるテストクラスを直接実行できないということです。まあそれはそういうものなら、ということで、JUnitのTestRunnerとかを使って自前でテストケースクラスを読み込んでrunさせるスクリプトを作ってそれを呼び出すようにします。

(案1)の次なる問題は、gmavenのスクリプト実行機能が、普通にクラスパスを指定する機能をもっていないということです。スクリプトであるGroovyコードを呼び出すことは出来るようです(scriptpath)。mavenのartifactをクラスパスに含めることも出来ます(classpath)。

でも、gmavenのスクリプト実行機能には、たとえばtarget/classesをクラスパスに追加設定する方法がありません(参考:"1""2")。
この結果スクリプトからテスト対象クラスを読み込むことが出来ないのですね。どうなんだこれはと私は思います。

しょうがないから、スクリプトを作って(runtest.groovy)、その中で以下のように動的にクラスパスを足してみます。

import org.jggug.sample.*;
import junit.textui.TestRunner;
import junit.framework.*

def loader = new GroovyClassLoader()
loader.addClasspath("./src/test/groovy")
loader.addClasspath("./src/main/groovy")

suite = new TestSuite()

["org.jggug.sample.FooTest", "org.jggug.sample.HogeTest"].each {
  suite.addTest(new TestSuite(loader.loadClass(it)))
}
TestResult result = TestRunner.run(suite)

上を呼び出すpomの設定は以下のとおり。

      <plugin>
        <groupId>org.codehaus.groovy.maven</groupId>
        <artifactId>gmaven-plugin</artifactId>
        <version>1.0-rc-3</version>
        <executions>
          <execution>
            <phase>test</phase>
            <goals>
              <goal>execute</goal>
            </goals>
            <configuration>
              <source>${pom.basedir}/src/test/groovy/org/jggug/sample/runtest.groovy</source>
            </configuration>
          </execution>
        </executions>
      </plugin>

以上、本来時間を全く使いたくない、ほんの簡単なことをしたいだけの設定のために時間を費やしたので、悔しいから書いておきます。なお、上記では当然surefireのレポート生成サポートは得られません。

(案2)でも同等のことが出来ます。たとえばこんな感じです。

      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>exec-maven-plugin</artifactId>
        <executions>
          <execution>
	    <id>test</id>
            <phase>test</phase>
            <goals>
              <goal>exec</goal>
            </goals>
            <configuration>
              <executable>groovy</executable>
              <arguments>
                <argument>-cp</argument>
                <argument>target/classes;src/main/groovy;src/test/groovy</argument>
                <argument>src/test/groovy/org/jggug/sample/runtest.groovy</argument>
              </arguments>
            </configuration>
          </execution>
        </executions>
      </plugin>

このようにtest phaseに引っ掛けます。とはいえ、スクリプトをexecuteする場合には、スクリプト中からfail()という特殊なメソッドを呼び出すことでビルドを失敗させることが出来きるのですが、exec-maven-pluginではできません。

教訓:groovycしとけ!*3

もしくは「Gradle使っとけ!」かな。Gradleで試験書いたことがないので、本当にそうかは不明。

上記、私はmavenに詳しくないので、うそを書いていたり、もっと良い方法があるよ、という場合ご指摘いただけるとありがたいです。

*1:番号は変わるのかもしれません

*2:単にgroovycしただけでは動かないときが経験上間々あります。class loaderの問題かなと思いますが詳細不明

*3:今回はそれではすまないので苦労してるのですけれども