2008年5月30日金曜日

惜しいリスト

Groovy は関数型言語のようにリストを扱えるようにはなっている. しかし惜しい...

例えば, [1,2,3] と [4,5,6] という二つのリストをマージして, [[1,4] ,[2,5] ,[3,6]] というようなリストを作りたいとする (変態なマージだけど).

普通に考えるとこんな感じになる.

def merge = { x, y, r = [] -> x == [] ? r : merge(x-x[0], y-y[0], r+[[x[0] , y[0]]]) }

まぁ記法がいいかどうかは別として, 意味は分かると思う. ところが Groovy1.5 でこれは動かないのだ. merge の定義中に再帰的に現れる merge を理解できないのだ. そこで次のようにすれば動く.

def merge; merge = { x, y, r = [] -> x == [] ? r : merge(x-x[0], y-y[0], r+[[x[0], y[0]]]) }

馬鹿でしょ. ま, いいけど.

メモ代わりに残しておく.

2008年5月13日火曜日

プラグインをテストする

Grails ではプラグインは強力な仕組みだけど, テストが難しい (場合がある). 特にアーティファクトのダイナミック・プロパティに依存するようなものはテストしにくい.

ここではごく簡単に, Controller に定義された特定のプロパティの値で振る舞いを変えるようなプラグインを考えてみる.

このようなプラグインをテストする最も簡単な方法は, テスト用のコントローラを定義して, ./grails-app/controllers/ の下に置き, integration test を実行することだ. でも, この方法だとテスト用のコントローラがプラグインのインストールを通してアプリケーションに入り込んでしまう. package-plugin 時に注意深くテスト用のコントローラを取り除くという手もなくはないけど, これは避けたい.

またテスト本来の目的からするとテスト用のコントローラがテストの外にファイルの形で存在するのは本当は嬉しくない. じゃあ, テストの中に定義してしまえということになる.

def gcl = new groovy.lang.GroovyClassLoader()

void setUp() {

    gcl.parseClass(

'''

class TestController {

    def action1 = { render "action1 called" }

}

'''

    )

}

このようにすると, テスト用のコントローラの定義もテスト・コード中に入れることが出来る. が, 残念なことにこのままでは TestController はコントローラ・アーティファクトとして認識されないので, 例えば render などというダイナミック・プロパティは注入されない.

そこで次の手は, こうやって書いたコントローラも正しいコントローラ・アーティファクトとして無理矢理認識させる方法だ. 実際, grails 本体のテスト・コードを見ると, このようなテストのための GroovyTestCase として, org.codehaus.groovy.grails.web.servlet.mvc.AbstractGrailsContollerTests がある. ただし, AbstractGrailsControllerTests は Test モジュールに入っているので, 普通の grails アプリケーションやプラグインからはアクセスできない.

じゃあ, このクラスをファイルごと自分のプロジェクトにコピーしてしまえ! (grails-1.0.2/test/groovy/org/codehaus/groovy/grails/web/servlet/mvc/AbstractGrailsControllerTests.groovy) 実はもう一つのクラス, MockGrailsPluginManager も必要になる. これも同じようにコピーしてくる (grails-1.0.2/test/commons/org/codehaus/groovy/grails/plugins/MockGrailsPluginManager.java). 置き場所は例えば test/integration 辺りでよい (パッケージに合わせてディレクトリを掘らなくてもよい). ただし, AbstractGrailsControllerTests の最初の import 文 (import org.codehaus.groovy.grails.commons.test.*) はエラーを起こすのでコメントアウトしておく必要がある.

で, さっきのようなテスト・クラスは

  • GroovyTestCase の直接のサブクラスではなく, 上記 AbstractGrailsControllerTests のサブクラスとする
  • 今まで setUp に書いていたようなことは onSetUp に書くようにする
  • 自分自身 (テスト対象のプラグイン) を AbstractGrailsControllerTests の setUp 中の dependantPluginClasses に追加しておく (例えば dependantPluginClasses <<>

すると, 上記 TestController はちゃんとコントローラ・アーティファクトとして認識されるようになる. つまり, render などが使えるようになる.

でもまだまだ残念なのだ. この Mock 環境では例えば Filter などはちゃんと (つまり run-app するのと同じようには) セットアップされないようなのである. つまり Filter にも何か追加しているようなプラグインのテストはやっぱり出来ない... (これは最初の方法でやっても結局同じ).

結局 webtest でもするしかないのである. この結論を得るためにどれだけ回り道をしたことか.

注:

最初の手法 (テストの外でコントローラを定義する) でもう少しマシなやり方もある. テスト・コントローラを正式な contollers ディレクトリではなく, test/integration/controllers 辺りに置くのである. その代わり, GrailsPlugin 定義の中で, watchedResources と onChange にも手を入れなければならないけど. このままプラグインをパッケージしても, まぁ許されるんじゃないか...?