Skip to content

Latest commit

 

History

History
328 lines (246 loc) · 7.72 KB

06_advanced.md

File metadata and controls

328 lines (246 loc) · 7.72 KB

高度な話題

old

oldを使うとwhenブロック前の値を参照できる。

setup:
def list = [1, 2, 3]

when:
list << 4

then:
list.size() == old(list.size()) + 1

JUnit連携

HamcrestのMatcherを使う

def "thatの使い方"() {
    def x = 10

    expect:
    that x, is(10)
    that x, equalTo(10)
}

def "thatのエイリアスのexpect"() {
    def x = 10

    expect:
    expect x, is(10)
}

def "that、expectを記述せずに使用することもできる"() {
    def x = 10

    expect:
    x is(10)
    x is(not(9))
}

カスタムのMatcherを使うのも簡単

Ruleを使う

Spockの独自機能で明示的にnewしなくともデフォルトコンストラクタでインスタンスが生成される。 コンストラクタの引数が必要な場合は、明示的にnewする必要がある。

@Rule
MyMethodRule myMethodRule1 // デフォルトコンストラクタで自動的にnewされる
@Rule
MyMethodRule myMethodRule2 = new MyMethodRule("MyMethodRule2") // 明示的にコンストラクタを指定して生成

@ClassRule
@Shared // @ClassRuleはstatic or @Sharedのフィールドのみに適用可能
MyClassRule myClassRule1
@ClassRule
@Shared
MyClassRule myClassRule2 = new MyClassRule("MyClassRule2")

JUnitのフィクスチャアノテーションを使う

@Before
void "before"() { ... }

@After
void "cleanup"() { ... }

@BeforeClass
static void "beforeClass"() { ... }

@AfterClass
static void "afterClass"() { ... }

JUnitのAssumeを使う

assumeを使って前提条件が満たされない場合にフィーチャメソッドを実行しない。

def "前提条件が満たされない場合はフィーチャメソッドが実行されない"() {
    assumeThat 1, equalTo(2)

    expect:
    false
}

MOP関連のサポート

@Use

@Useをフィーチャメソッドに付与すると、フィーチャメソッド内がuseブロックのスコープとなる。

class UseSpec extends Specification {

    @Use(StringExtension)
    def "拡張モジュールを使う"() {
        expect:
        "groovy".duplicate() == "groovygroovy"
    }

    @Use(IntegerCategory)
    def "カテゴリークラスを使う"() {
        expect:
        7.square() == 49
    }

    @Use([StringExtension, IntegerCategory])
    def "複数指定する"() {
        expect:
        "groovy".duplicate() == "groovygroovy"
        7.square() == 49
    }
}

class StringExtension {
    static String duplicate(String self) { self * 2 }
}

@Category(Integer)
class IntegerCategory {
    Integer square() { this * this }
}

@ConfineMetaClassChanges

@ConfineMetaClassChangesを使うとmetaClassの状態をリストアできる。 フィーチャメソッドに@ConfineMetaClassChangesを付与するとsetupメソッドの後のmetaClass状態にcleanupメソッドの前でリストアされる。

@ConfineMetaClassChanges([String, Integer])
def "メタクラスを操作する"() {
    setup: "この操作はロールバックされる"
    String.metaClass.duplicate = { -> delegate * 2 }
    Integer.metaClass.square = { -> delegate * delegate }

    expect:
    "hello".duplicate() == "hellohello"
    2.square() == 4
}

スペッククラスに@ConfineMetaClassChangesを付与すると、setupSpecメソッドの前のmetaClass状態にcleanupSpecメソッドの後でリストアされる。

// このスペッククラス内でのStringに対するmetaClassの操作は
// このスペックが終了するときにロールバックされる
@ConfineMetaClassChanges(String)
class ConfineMetaClassChangesWithClassSpec extends Specification {
    ...
}

任意のフィーチャメソッドを実行する

参考: 任意のフィーチャーメソッドだけを実行する - bluepapa32's Java Blog

まずはアノテーションを用意する。

@Target([ElementType.TYPE, ElementType.METHOD])
@Retention(RetentionPolicy.RUNTIME)
public @interface Fast {}

@Target([ElementType.TYPE, ElementType.METHOD])
@Retention(RetentionPolicy.RUNTIME)
public @interface Slow {}

@Target([ElementType.TYPE, ElementType.METHOD])
@Retention(RetentionPolicy.RUNTIME)
public @interface Server {}

アノテーションをスペッククラスやフィーチャメソッドに付与する。

@Server
class IncludeExcludeSpec extends Specification {

    def "server"() { ... }

    @Slow
    def "slow"() { ... }

    @Fast
    def "fast"() { ... }
}

設定ファイルを準備する。

// Firstが付与されているものだけを実行
runner {
    include Fast
}

このファイルを以下のいずれかで読み込ませる。

  • システムプロパティspock.configurationで指定
    • 例: -Dspock.configuration=MySpockConfig.groovy
  • クラスパス上のSpockConfig.groovyに配置
  • $HOME/.spock/SpockConfig.groovyに配置

IDE上でクラスパスにSpockConfig.groovyを配置する場合はコンパイルされないように工夫が必要。

Slowが付与さているものだけ除外する場合

runner {
    exclude Slow
}

Serverが付与されているものの中でFastとSlowを除外する場合

runner {
    include Server
    exclude Fast, Slow
}

スペック(ベースでもOK)クラスを指定する場合

runner {
    include IncludeExcludeSpec
}

これで特定のクラスを継承したスペッククラスを対象とする、または除外するといったことが可能になる。

非同期処理のテスト

AsyncConditionsBlockingVariable(s)などがあるが、ver0.7で追加されたポーリングコンディションを実現するPollingConditionsが使いやすい。

def "PollingConditionsの基本的な使い方"() {
    setup:
    PollingConditions conditions = new PollingConditions()
    def number = 0

    when:
    Thread.start {
        number = 1
    }

    then: "eventuallyのコンディションが満たされるまで待ち合わせる"
    conditions.eventually {
        number == 1
    }
}

@FailsWith(SpockTimeoutError)
def "タイムアウトになるとSpockTimeoutErrorが発生する"() {
    setup:
    PollingConditions conditions = new PollingConditions() // デフォルトではタイムアウト値が1秒
    def number = 0

    when:
    Thread.start {
        Thread.sleep(1100) // 1秒以上スリープ
        number = 1
    }

    then: "1秒待ってタイムアウト"
    conditions.eventually {
        number == 1
    }
}

def "タイムアウト値を設定する"() {
    setup: "タイムアウト値を2秒に設定"
    PollingConditions conditions = new PollingConditions(timeout: 2)
    def number = 0

    when:
    Thread.start {
        number = 1
    }

    then:
    conditions.eventually {
        number == 1
    }
}

def "withinを使ってタイムアウト値を設定する"() {
    setup:
    PollingConditions conditions = new PollingConditions()
    def number = 0

    when:
    Thread.start {
        number = 1
    }

    then: "withinでタイムアウトを2秒に設定"
    conditions.within(2000) { // withinを使うとブロックのタイムアウト値だけ変更できる
        number == 1
    }
}

ただし、ver0.7の時点ではPollingConditions@Beta扱いのため、今後変更される可能性もある。