SpockでビルトインされているExtensionsとかそのへん
G* Advent Calendar 2012 12日目担当のyamkazuです。こんにちは。
今日はみんな大好きSpockでビルトインされている機能拡張について、いくつかピックアップして紹介します。機能拡張にカテゴライズされないものもあるかもしれませんが、その辺はゆるやかに。
また、この記事はSpock0.7を元に記述していますが、バージョンが変わるとアノテーションが存在しないとかありますので、新しいバージョンが出た場合はそのへんを注意してお読みください。
それではさっそく。
@Ignore
これは説明不要だと思いますが、Ignoreを付与すると指定したフィーチャの実行がスキップされます。アノテーションに理由を書くような使い方もできます。
@Ignore def "xxx"() { expect: true } @Ignore('hogehogeのため') def "yyy"() { expect: true }
スペックに指定すると全体がスキップされます。
@Ignore class IgnoreSpec extends Specification { ... }
@IgnoreRest
IgnoreRestはIgnoreとは対照的に、IgnoreRestが付与されたフィーチャのみを実行します。IDEを使っている場合は対象のフィーチャを決め打ちで実行するのは比較的容易なのですが、コンソールからtestを実行する場合する場合などはそうではありません。このアノテーションを使用すると実行したいフィーチャメソッドを簡単に指定することができます。
class IgnoreRestSpec extends Specification { def "xxx"() { ... } @IgnoreRest def "yyy"() { ... } @IgnoreRest def "zzz"() { ... } }
上記のように複数指定することもでき、この例ではyyy、zzzのみが実行されます。
@IgnoreIf
IgnoreIfは指定されたクロージャの実行結果がtrueの場合にフィーチャの実行がスキップされます。クロージャの中では暗黙的に以下の変数が使用可能です。
- env - System.getenv()のショートカット
- properties - System.getProperties()のショートカット
- javaVersion - Javaのバージョン
以下のように使用します。
@IgnoreIf({ true }) def "trueなので実行されない"() { expect: false } @IgnoreIf({ false }) def "falseなので実行される"() { expect: true } @IgnoreIf({ 1 < 2 }) def "1 < 2 はtrueなので実行されない"() { expect: false } @IgnoreIf({ 1 > 2 }) def "1 > 2 はfalseなので実行される"() { expect: true } @IgnoreIf({ def a = 1 def b = 1 a + b == 2 }) def "closureをcallしているだけなので複数行書いても良い"() { expect: false } @IgnoreIf({ javaVersion > 1.6 }) def "javaVersionでJVMのバージョンが参照できる"() { expect: false } @IgnoreIf({ env["LANG"] != 'C' }) def "envがSystem.getenv()のショートカットになっている"() { expect: false } @IgnoreIf({ properties["os.name"] == 'Mac OS X' }) def "propertiesがSystem.getProperties()のショートカットになっている"() { expect: false }
@FailsWith
Spockでは例外のテストを行う際は、以下のように
then: MyException e = thrown()
thrown()
を使用することができますが、FailsWithはいわゆるJUnit4の@Test(expected = MyException.class)
のような記述の仕方を可能にするアノテーションで、指定した例外でフィーチャが失敗することを宣言できます。
@FailsWith(MyException) def "xxx"() { expect: throw new MyException() } @FailsWith(value = MyException, reason = "hogehogeのため") def "yyy"() { expect: throw new MyException() }
スペックに付与することも可能です。
@FailsWith(MyException) class FailWithSpec extends Specification { ... }
この場合は、スペック上のすべてのフィーチャが指定した例外で失敗することを宣言しています。
@Timeout
Timeoutはフィーチャの実行時間のタイムアウト値を指定することができます。このタイムアウト値を超過した場合はorg.spockframework.runtime.SpockTimeoutError
がスローされます。
@Timeout(1) def "1秒以内に終わる"() { expect: Thread.sleep 500 } @FailsWith(SpockTimeoutError) @Timeout(1) def "1秒以内に終わらない"() { expect: Thread.sleep 1100 }
デフォルトでは単位は秒に設定されています。単位を変更したい場合はunit属性を指定します。
@Timeout(value = 500, unit = TimeUnit.MILLISECONDS) def "500ミリ秒以内に終わる"() { expect: Thread.sleep 250 } @FailsWith(SpockTimeoutError) @Timeout(value = 250, unit = TimeUnit.MILLISECONDS) def "500ミリ秒以内に終わらない"() { expect: Thread.sleep 300 }
@Unroll
通常Spockではwhereのパラメタライズテストを実行すると、そのフィーチャに対し1つの実行結果が出力されます。
def "x + y の合計を計算する"() { expect: x + y == sum where: x | y || sum 1 | 2 || 3 3 | 4 || 7 5 | 6 || 11 }
実行結果
Test Duration Result
x + y の合計を計算する 0.001s passed
Unrollはこのパラメタライズテストをそれぞれの独立したフィーチャとして実行してくれます。また以下のように#でパラメータをフィーチャ名に埋め込むことができます。
@Unroll def "#x + #y の合計は #sum になる"() { ... }
実行結果
Test Duration Result 1 + 2 の合計は 3 になる 0s passed 3 + 4 の合計は 7 になる 0s passed 5 + 6 の合計は 11 になる 0s passed
#形式での参照は引数なしのメソッドあれば、メソッドをチェインして参照することも可能です。詳細はリファレンスを参照してください。
@Shared
通常フィールドで宣言したフィクスチャはフィーチャの実行毎に初期化されます。
def counter = 0 def "counterをインクリメントする"() { expect: counter++ == expectedCounter where: expectedCounter << [0, 0, 0] }
Sharedはフィーチャ間でフィクスチャを共有するSharedFixtureを実現してくれます。生成コストが高いオブジェクトをフィーチャ間で共有したい場合に便利です。
@Shared def counter = 0 def "counterをインクリメントする"() { expect: counter++ == expectedCounter where: expectedCounter << [0, 1, 2] }
@AutoCleanup
AutoCleanupはフィールドに設定することで自動で後処理をしてくれます。デフォルトではcloseメソッドが自動で呼びされます。
@AutoCleanup def closeable = new Closeable()
明示的に呼び出す後処理のメソッドを指定することもできます。
@AutoCleanup("shutdown") def shutdownable = new Shutdownable()
後処理の中に例外が発生した場合に発生した例外を握りつぶしたい場合はquiet属性にtrue
を指定します。デフォルトはfalse
です。
@AutoCleanup(value = 'shutdown', quiet = true) def shutdownable = new Shutdownable()
これらの後処理はフィーチャ実行毎に実行されますが、@Sharedが付与されたフィールドでは全フィーチャの実行後に1度だけ実行されます。
@Stepwise
Stepwiseを使用すると一連のフィーチャをそれぞれ定義した順に実行してくれます。フィーチャが一連のシナリオとして実行されるイメージで、途中のテストが失敗すると以降のテストが実行されません。
@Stepwise class StepwiseSpec extends Specification { def "first"() { expect: true } def "second"() { expect: false } def "third"() { expect: true } }
上記の例ではfirst、second、thirdの順に実行されるはずですが、secondで失敗するため、thirdは実行されません。