Spock は素晴らしい!のですが、最大限機能を生かしきれてないと、よく感じるのでメモ。

基本形

package net.bucyou

import spock.lang.Specification

class SampleSpecification extends Specification {

    // すべての feature (テスト) 前に実行される
    def setup() {

    }

    // すべての feature (テスト) 後に実行される
    def cleanup() {

    }

    // 最初の feature (テスト) 前に実行される
    def setupSpec() {
    }

    // 最後の feature (テスト) 後に実行される
    def cleanupSpec() {
    }

    // feature method (テスト)
    def "うどんをたべるとうまい"() {
        given:
        def i = ..
        def udon = ..

        when:
        def result = i.eat(udon);

        then:
        result == "Umai"
    }
}

うどんはうまい。
このとき、result == “Mazui” とかを仮に結果に入れてみると、やたら詳しく比較が出てくれます。
これは、IntelliJ IDEA の機能ではなく、Spock の機能のようです。

スクリーンショット 2013-08-16 17.55.30

Map や List でも、同様に細かい比較を見ることができるので、大助かりであります。

featureの文法

feature は、setup, when, then, expect, cleanup, そして where
6つのブロックによって構成されています。
いつも、Gherkin の影響で given を使っていたのですが、
これは、setup の エイリアスとして定義されているので、動作に問題はありません。

Blocks2Phases

公式のドキュメントによると、流れは上のような感じ。

setup: データの準備とか (1)
when: 実際に何かを実行 (2)
then: 結果の検証 (3)
expect: 何かを実行して検証 (2,3)
cleanup: データのお掃除とか (4)
where: なんか図では、えらいことになってますが後述

then, when など順番を逆に書いたりすると、エラーって落ちたりするっぽいです。
when -> expect -> then というものダメです。
when, then, when, then と複数回書くのは問題ないようです。
then が前のブロックに続けて複数あるときなどは and も使えるようです。

where block

where は複数の入力データを、同じテスト (setup, when, then, cleanup) に通すときに便利なブロックです。

class SampleSpecification extends Specification {

    def "where!!"() {
        setup:
        println "setup"

        when:
        println "when"

        then:
        println "then"
        println "a=" + a
        println "b=" + b

        cleanup:
        println "cleanup"

        where:
        a | b
        1 | "Udon"
        2 | "Soba"
    }
}

これを実行すると、以下の様な結果がやってきます。

setup
when
then
a=1
b=Udon
cleanup
setup
when
then
a=2
b=Soba
cleanup
class SampleSpecification extends Specification {

    def "where!!"() {
        setup:
        println "setup"

        when:
        println "when"

        then:
        println "then"
        println "a=" + a
        println "b=" + b

        cleanup:
        println "cleanup"

        where:
        a | b
        1 | "Udon"
        2 | "Soba"
    }
}

こういう、表形式な書き方もできるので、テストをわかりやすくするためには
なかなかゴキゲンになれる機能ですね!

Mock については、また今度書きます。

gradle-icon-512x512

オレオレGradleメモだよー

お仕事ではGroovy 使いかつ、XMLをチマチマ書いていくのが嫌いなので、
Maven や Ant ではなく、Gradle をビルドに使っています。

Java プロジェクトの場合は、プロジェクトのルートに下のような
build.gradle を準備して、Maven と同じ ディレクトレイアウトを
作ってやるだけで良いというのは、なかなか簡単です。

apply plugin: 'java'

repositories {
    mavenCentral()
}

dependencies {
    testCompile group: 'junit', name: 'junit', version: '4.+'
}

あとは依存追加したり、テストカバレッジを計測するために、
gradle-cobertura-plugin を導入したりしています。

課題

実装しているものの都合上、外部システムにHTTPでリクエストして
データを取得するというクラスが結構多いのですが、UnitTest の場合は
大抵、Mock にお願いする感じです。
(テストは Spock で書いていますので、Spock の Mock を活用する感じです。)

でも、たまに実際に、HTTPリクエストして返ってきたものを検証したい
という事もあり、HttpClient を Mock に差し替えず、実際に HTTP リクエストを
投げるテストも書いていたりします。

これを単純に src/test 下で書いてしまうと、 gradle build したときに、ネットワークや
データの都合などで見事にビルドが失敗したりします。
結果、Jenkinsのオッサンはその度に怒り狂い、平謝りをすることになります。

Jenkinsのオッサンには、UnitTest と、IntegrationTest的なものは
別々のタスクとして実行して貰いたい。ということで、新たにタスクを定義したい
わけです。

パッケージで分ける方法

とりあえず、ってことで今はパッケージで分ける方法を使っています。

apply plugin: 'java'

repositories {
    mavenCentral()
}

dependencies {
    testCompile group: 'junit', name: 'junit', version: '4.+'
}

test {
    exclude 'net/bucyou/integration/**'
}

task integrationTest(type:Test) {
    include 'net/bucyou/integration/**'
}

これで、 net.bucyou.integration.* にあるテストは build で実行されなくなります。
一方、integrationTest タスクを対象のパッケージ下のみをテストするように定義
しました。これで分離出来ました。

プラグイン使用+新しいディレクトリ分類を作る

よくよく探すと、integration-test plugin がありました。

これを使うと、 src/integration-test/(java|groovy|scala|resources) 
integrationTest のためのディレクトリとして捉えたタスクを追加してくれるっぽいです。

最初からこれ使ってればよかった・・・。

Jensen_Interceptor_III

NOTE(2020/09/03): この記事は2013年に書かれており、古い内容になっています。現在のGroovyの実装と異なる場合があります。

いつも、PHPのことを話している人と思っているだろう。
実は、現在の大半のお仕事は Groovy によって行われているのだよ。

ブログでは初の Groovy ネタです。1年ほど、Groovy を触っているのですが、
私のような PHPer でも (一応、Javaの経験は昔にある) すんなり触ることができる
素晴らしい言語です。

大体のことは、プログラミングGroovy で学べました。
良い本です。

今回は、Groovy の Interception についてメモです。

NOTE (2020/09/03): Groovyをホスティングしていた、Codehaus が2015年にサービス終了してしまったために、リンク切れとなっていました。現在のGroovyドキュメントに当該の記述が見つからなかったため、参考リンクとして以下を貼ります。
Internet Archive へのリンク
Interceptor のテストコード
API Doc (Interceptor)

Interception と言えば、Spring Framework なんかにある、特定のメソッドの
実行前と、実行後に何らかのロジックを仕込むアイツです。
PHP で言えば、BEAR.Sunday が、この機構を見事に持ってます。

AOP 的には、本質 (実際のロジック) でない横断的な関心事 (実行時間計測・ロギング)
は、本質とは別に実装せよ。とのことらしいです。

というわけで、実際のコードを確認。

まずは、こんなコードを用意して、GroovyConsole あたりで実行してみましょう。

class Hello {
  def name
  Hello(def name) {
    this.name = name
  }
  void sayHello() {
    println "Hello, ${name}."
  }
}

def h = new Hello("bucyou")
h.sayHello() // Hello, bucyou.

この、クラスに対して、Interceptor を差し込みます。

class Hello {
  def name
  Hello(def name) {
    this.name = name
  }
  void sayHello() {
    println "Hello, ${name}."
  }
}

class MyInterceptor implements Interceptor {
  // 前処理
  Object beforeInvoke(Object object, String methodName, Object[] argments) {
    println "beforeInvoke ${methodName}"

    null
  }

  // 自動的にメソッドを invoke する場合は true
  boolean doInvoke() { true }

  // 後処理 メソッド実行の結果を result で改変できる
  Object afterInvoke(Object object, String methodName, Object[] argments, Object result) {
    println "afterInvoke ${methodName}"

    result
  }
}

def proxy = ProxyMetaClass.getInstance(Hello);
proxy.interceptor = new MyInterceptor();

// ここは interceptor による割り込みはない
def h1 = new Hello("bucyou");
h1.sayHello();

// use を使った部分だけ interceptor により割り込み有り
proxy.use {
    def h2 = new Hello("bucyou2");
    h2.sayHello() // Hello, bucyou.
}

そうすると、出力結果としては

Hello, bucyou.
beforeInvoke ctor
afterInvoke ctor
beforeInvoke sayHello
beforeInvoke println
Hello, bucyou2.
afterInvoke println
afterInvoke sayHello

となります。メソッド名で振り分けているわけではないので、コンストラクタや
println() でも、beforeInvoke, afterInvoke が走っています。
Advisor 的なものを作れば、Groovy 単体で、十分に機能する Interceptor
実装が出来るかもしれません。