この記事は筆者のソロ Advent Calendar 2022 3日目の記事です。
KotlinプロジェクトにテスティングフレームワークであるSpockを導入するための方法を紹介していきます。
KotlinでSpock入門[基礎編]
KotlinでSpock入門[基礎編2]
KotlinでSpock入門[Data Driven Testを学んでデータ駆動テストを使い倒す] <- 今ここ
KotlinでSpock入門[Data Driven Testを学んでデータ駆動テストを使い倒す2]
KotlinでSpock入門[mock編]
KotlinでSpock入門[Spring boot実践編]
今回はData Driven Testingについて記載していきます!
記事作成で使用したサンプルコードはこちら
Data Driven Testing is 何??
入力と出力のパターンを列挙してテストする方法のことという理解。JUnitであればParameterizedTest, KotestであればData Driven Testing, GoであればTableDrivenTestと呼ばれ方が微妙に違うが全て同じテスト手法のことを指していると思ってます。GoではこのようなTableDrivenTestが明確に推奨されている。SpockのドキュメントではData Driven Testingと記載されている。
データテーブルを作る
まず、以下のようなテストケースがあるとします。
def "maximum of two numbers"() {
expect:
Math.max(1, 3) == 3
Math.max(7, 4) == 7
Math.max(0, 0) == 0
}
これで、テストケースとしては特段問題はありませんがコードとデータが混在していて変更が少し容易ではないです。そして、コードの重複が少し気になります。テストが失敗した時もどの入力が原因かを突き止めるのが難しいかもしれません。これをデータ駆動テストで書いてみると以下のようになります。
def "maximum of two numbers"(int a, int b, int c) {
expect:
Math.max(a, b) == c
where:
a | b | c
1 | 3 | 3
7 | 4 | 7
0 | 0 | 0
}
featureメソッドの引数に入力と期待される出力を指定し(これをデータ変数と呼ぶ)、whereブロックにデータテーブルを記述していく。テーブルの最初の行はテーブルヘッダと呼び、データ変数を宣言します。続く行はテーブル行と呼び、データ変数に対応する値を指定する。各行に対してfeatureメソッドが1回実行され、どこかで失敗したとしても全ての行が実行される。
データテーブルは最低2列必要であり、1列のテーブルは下記のように書ける。
where:
a | _
1 | _
7 | _
0 | _
テーブルは以下のように分割して書くこともできる。
where:
a | _
1 | _
7 | _
0 | _
__
b | c
1 | 2
3 | 4
5 | 6
連続したアンダースコアはwhereブロックではテーブルの分割以上の役割はなく、処理としては無視されるためスタイリングの要素としても使用することができる。
def "maximum of two numbers2"(int a, int b, int c) {
expect:
Math.max(a, b) == c
where:
____
a | _
1 | _
7 | _
0 | _
____
b | c
3 | 3
4 | 7
0 | 0
}
上述したコードはいくつか手を加えることができます。whereブロックでデータ変数を宣言しているためfeatureメソッドの引数は省略できます。入力と出力を視覚的に区別しやすくするために||で区切ることができます。
def "maximum of two numbers3"() {
expect:
Math.max(a, b) == c
where:
a | b || c
1 | 3 || 3
7 | 4 || 7
0 | 0 || 0
}
セミコロン区切りで以下のようにも書けます。
def "maximum of two numbers4"() {
expect:
Math.max(a, b) == c
where:
a ; b ;; c
1 ; 3 ;; 3
7 ; 4 ;; 7
0 ; 0 ;; 0
}
カラムセパレーターが変更されると新しい独立したデータテーブルが開始されるため以下のような書き方もできる。
def "maximum of two numbers5"() {
expect:
Math.max(a, b) == c
Math.max(d, e) == f
where:
a | b || c
1 | 3 || 3
7 | 4 || 7
0 | 0 || 0
d ; e ;; f
1 ; 3 ;; 3
7 ; 4 ;; 7
0 ; 0 ;; 0
}
RollupとUnroll
@Rollupアノテーションをつけると反復処理が独立して報告されるのではなく、機能の中で集計されます。具体的には以下のようになります。
@UnrollアノテーションはSpock2.0以降デフォルトで有効になっているみたいで普通にテストを実行すれば各データ行のレポートがそれぞれ出力される。
データパイプ
データ変数に値を供給するには、テーブルだけでなくデータパイプを使用することもできる。
書き方はデータ変数 << [値,...]
のように書く。
def "maximum of two numbers6"() {
expect:
Math.max(a, b) == c
where:
a << [1, 7, 0]
b << [3, 4, 0]
c << [3, 7, 0]
}
まとめ
以下について紹介しました。
- データテーブルの作成の仕方とSpockによるデータ駆動テストの仕方
- Rollup(Unroll)アノテーションの使い方
- データパイプによるデータの注入の仕方
次回もSpockにおけるデータ駆動テストについて書いていこうと思います、以上!