1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

panda(Kotlin, Test, BlockChain)Advent Calendar 2022

Day 4

KotlinでSpock入門[Data Driven Testを学んでデータ駆動テストを使い倒す2]

Last updated at Posted at 2022-12-03

この記事は筆者のソロ Advent Calendar 2022 4日目の記事です。

引き続きSpockの使い方を紹介していきます。今回は前回の続きでDataDrivenTestについて書いていきます。

KotlinでSpock入門[基礎編]
KotlinでSpock入門[基礎編2]
KotlinでSpock入門[Data Driven Testを学んでデータ駆動テストを使い倒す]
KotlinでSpock入門[Data Driven Testを学んでデータ駆動テストを使い倒す2] <- 今ここ
KotlinでSpock入門[mock編]
KotlinでSpock入門[Spring boot実践編]

記事作成で使用したサンプルコードはこちら

多変数データパイプ

前回データテーブルだけでなくデータパイプをwhereブロックに指定することでデータ変数に値を注入できることを述べたが、DBのデータを直接注入したりもできる。

@Shared sql = Sql.newInstance("jdbc:h2:mem:", "org.h2.Driver")

def "maximum of two numbers"() {
  expect:
  Math.max(a, b) == c

  where:
  [a, b, c] << sql.rows("select a, b, c from maxdata")
}

使用しない変数はアンダーラインで使用しないようにできる。

where:
[a, b, _, c] << sql.rows("select * from maxdata")

データパイプの名前付き分解

パイプは以下のように分解することで見やすくしたりもできる。

    def "multi deta pipe test"() {
        expect:
        a + b == c
        where:
        [a, b, c] << [
            [
                    a: 1,
                    b: 3,
                    c: 4
            ],
            [
                    a: 2,
                    b: 4,
                    c: 6
            ]
        ]
    }

データ変数への値の代入

データ変数には値を直接代入することもできる。

    def "test data variable assignment"() {
        expect:
        a + b == c
        where:
        a = 3
        b = 1
        c = 4
    }

実際は以下のようにDBから取得したデータを変数に直接代入したりするときに使える。

where:
row << sql.rows("select * from maxdata")
// pick apart columns
a = row.a
b = row.b
c = row.c

別のデータ変数へのアクセス

別のデータ変数にアクセスする方法は2つ。
1つは上述したような直接代入したデータ変数は別の箇所で使用することができる。

    def "test data variable assignment"() {
        expect:
        a + b == c
        where:
        value << [1, 2, 3]
        a = value
        b = value + 1
        c = a + b
    }

2つ目はデータテーブル内の以前のカラムにアクセスする方法である。また、同じwhereブロック内の過去のデータテーブルの列も含まれるため以下のようにも書ける。

    def "test Accessing Other Data Variables"() {
        expect:
        b - a == 1
        c + d == e
        where:
        a|b
        1| a + 1
        2| a + 1
        3| a + 1
        and:
        c = 1
        and:
        d | e
        b | d + 1
        b | d + 1
        b | d + 1
    }

データ変数への分割代入

反復処理できるオブジェクトであればパイプを使わず以下のように標準的なGroovyの複数代入の構文を使用することもできる。

@Shared sql = Sql.newInstance("jdbc:h2:mem:", "org.h2.Driver")

def "maximum of two numbers multi-assignment"() {
  expect:
  Math.max(a, b) == c

  where:
  row << sql.rows("select a, b, c from maxdata")
  (a, b, c) = row
}

データテーブル、パイプ、データ変数への代入の組み合わせ

以下のように複数組み合わせることも可能。

where:
a | b
1 | a + 1
7 | a + 2
0 | a + 3

c << [3, 4, 0]

d = a > c ? a : c

型変換

featureメソッドのパラメーター引数に型宣言をすることで暗黙的にデータ変数への型変換を行える。以下の例ではIntegerで宣言したデータ変数iにwhereブロックで文字列を注入しているがexpectブロックではちゃんと型変換されIntegerとなっている。

    def "type coercion for data variable values"(Integer i) {
        expect:
        i instanceof Integer
        i == 10

        where:
        i = "10"
    }

また、@Useアノテーションを使用しコンバーターのようなものを適用することもできる。以下の例ではBar型で宣言されたデータ変数にBaz型の値を注入しているがexpectブロックではBar型の値になっている。これを実現しているのはBaz型をBar型に変換するCoerceBazToBarというクラスであり、これを@Useに指定している。

@Use(CoerceBazToBar)
class Foo extends Specification {
    def foo(Bar bar) {
        expect:
        bar == Bar.FOO

        where:
        bar = Baz.FOO
    }
}
enum Bar { FOO, BAR }
enum Baz { FOO, BAR }
class CoerceBazToBar {
    static Bar asType(Baz self, Class<Bar> clazz) {
        return Bar.valueOf(self.name())
    }
}

テストケースの命名

Spockの反復テストはデフォルトで以下のような表示になります。

    def "maximum of two numbers7"() {
        expect:
        Math.max(a, b) == c

        where:
        a ; b ;; c
        1 ; 3 ;; 3
        7 ; 4 ;; 7
        0 ; 0 ;; 0
    }

image.png

データ変数をメソッド名の中で使用することができ、使用するには#変数名の形式で使用することができる。

    def "when num1: #a num2: #b, max is #c"() {
        expect:
        Math.max(a, b) == c

        where:
        a ; b ;; c
        1 ; 3 ;; 3
        7 ; 4 ;; 7
        0 ; 0 ;; 0
    }

image.png

展開する変数はオブジェクトでも可能で、プロパティアクセスや引数なしのメソッド呼び出しもかのう。

def "#person is #person.age years old"() { // property access
def "#person.name.toUpperCase()"() { // zero-arg method call

以下のような引数ありのメソッド呼び出しや計算式などはNG。

def "#person.name.split(' ')[1]" {  // cannot have method arguments
def "#person.age / 2" {  // cannot use operators

もし、上記のような複雑な処理の値を使用したい場合は以下のように変数を追加することもできる。

def "#lastName"() {
  ...
  where:
  person << [new Person(age: 14, name: 'Phil Cole')]
  lastName = person.name.split(' ')[1]
}

組み込みのデフォルトで使える以下の変数もある。

  • #featureName featureメソッド名が展開されるので全て同じ名前で表示したい場合に使う。
  • #iterationIndex イテレーションのインデックス番号を展開できる。
  • #dataVariables x: 1, y: 2, z: 3のようにデータ変数を展開する。
  • #dataVariablesWithIndex x: 1, y: 2, z: 3, #0のようにインデックス付きでデータ変数を展開する。

@Unrollアノテーションを使用して以下のようにも書ける。

    @Unroll("#featureName[#iterationIndex] #a + #b = #c")
    def "maximum of two numbers7"() {
        expect:
        Math.max(a, b) == c

        where:
        a ; b ;; c
        1 ; 3 ;; 3
        7 ; 4 ;; 7
        0 ; 0 ;; 0
    }

image.png

まとめ

前回Data Driven Testについての基礎的な内容を紹介しましたが、今回は以下の内容を紹介しました。

  • DBなどの外部リソースからのデータの注入方について。
  • データ変数のさまざまな取り扱いの仕方について。
  • featureのさまざまな命名の方法について。

筆者は初めてJUnitでパラメーターテストを知ったときはこんなテストの書き方があるんだとめちゃくちゃ感動しました。Spockはこのようなデータ駆動的なテストが簡潔に書きやすいと感じました!今回は以上です!

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?