かんがるーさんの日記

最近自分が興味をもったものを調べた時の手順等を書いています。今は Spring Boot をいじっています。

Spring Boot 1.4.x の Web アプリを 1.5.x へバージョンアップする ( 番外編 )( Groovy + JUnit4 でテストを書いてみる、Groovy SQL を使ってみる )

概要

記事一覧こちらです。

Groovy でテストを書く場合 Spock を使用していますが、Spring Boot 1.4.x の Web アプリを 1.5.x へバージョンアップする ( 番外編 )( static メソッドをモック化してテストするには? ) で JUnit4 形式でも書けることに気付いたので、Groovy + JUnit4 で書く場合のサンプルを作ってみます。

また Spock をもっと使えるようにしたいと思い、最近以下の本を購入して読んでいるのですが、

Java Testing With Spock

Java Testing With Spock

Spock: Up and Running: Writing Expressive Tests in Java and Groovy

Spock: Up and Running: Writing Expressive Tests in Java and Groovy

Groovy SQL というものがあることを知りました。DbUnit もいいのですが、DB のデータを手軽に確認したい場合には少し手間がかかるので、Groovy SQL を利用して簡単に確認する方法もサンプルに入れてみます。

参照したサイト・書籍

  1. groovy.sql - Class Sql
    http://docs.groovy-lang.org/latest/html/api/groovy/sql/Sql.html

  2. Groovyで楽にSQLを実行してみよう
    https://www.slideshare.net/simosako/db-16699219

    • Groovy SQL の使い方が分かりやすくまとまっていて非常に参考になりました。

目次

  1. build.gradle を修正して groovy-all を依存関係に追加する
  2. Java + JUnit4 ではなく Groovy + JUnit4 でテストを書いた時に便利な点とは
    1. Groovy では .class は不要
    2. テストメソッド名を “” で囲んで自由に書ける
    3. テストメソッドに throws Exception の記述が不要
    4. Spcok でなくてもテストメソッド内で given:, when:, then:setup:, expect: が書ける(単なるコメントとしてだけのようですが)
    5. Spock の時と同様に PowerAssert の分かりやすいレポートが出せる
  3. Groovy + JUnit4 + Groovy SQL のサンプルを書いてみる
  4. Groovy SQL、IntelliJ IDEA + Groovy SQL で書くと便利な点とは
    1. SQL 文に IntelliJ IDEA の SQL の Code Style が適用される
    2. なぜかメソッドの引数の SQL 文に、テーブル名、カラム名等の補完が効く
    3. SQL 文を書く時にヒアドキュメントが使える
  5. 最後に

手順

build.gradle を修正して groovy-all を依存関係に追加する

以前コンパイルした時にエラーが出たため build.gradle で groovy-all を除外していたのですが、このままでは groovy SQL が使えませんでした。使えるようにするために groovy-all を依存関係に追加します。

まず dependencies の testCompile("org.spockframework:spock-core:${spockVersion}"), testCompile("org.spockframework:spock-spring:${spockVersion}") のところに書いていた { exclude module: "groovy-all" } を削除します。

dependencies {

    ..........

    testCompile("org.spockframework:spock-core:${spockVersion}")
    testCompile("org.spockframework:spock-spring:${spockVersion}")

    ..........

}

変更した後、Gradle Tool Window の左上にある「Refresh all Gradle projects」ボタンをクリックして更新します。

更新後 clean タスク → Rebuild Project を実行すると以下のエラーが出ます。

f:id:ksby:20170617154842p:plain

以前はこれを見て groovy-all の方を除外してしまったのですが、groovy 方を除外するようにします。コマンドラインから gradlew dependencies を実行して出力された結果を見ると、org.codehaus.groovy:groovy を依存関係に入れているのは org.springframework.boot:spring-boot-starter-thymeleaf でした。

+--- org.springframework.boot:spring-boot-starter-thymeleaf: -> 1.5.4.RELEASE
|    +--- org.springframework.boot:spring-boot-starter:1.5.4.RELEASE (*)
|    +--- org.springframework.boot:spring-boot-starter-web:1.5.4.RELEASE (*)
|    +--- org.thymeleaf:thymeleaf-spring4:2.1.5.RELEASE -> 3.0.6.RELEASE
|    |    +--- org.thymeleaf:thymeleaf:3.0.6.RELEASE
|    |    |    +--- ognl:ognl:3.1.12
|    |    |    |    \--- org.javassist:javassist:3.20.0-GA -> 3.21.0-GA
|    |    |    +--- org.attoparser:attoparser:2.0.4.RELEASE
|    |    |    +--- org.unbescape:unbescape:1.1.4.RELEASE
|    |    |    \--- org.slf4j:slf4j-api:1.6.6 -> 1.7.25
|    |    \--- org.slf4j:slf4j-api:1.6.6 -> 1.7.25
|    \--- nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect:1.4.0 -> 2.2.2
|         +--- nz.net.ultraq.thymeleaf:thymeleaf-expression-processor:1.1.3
|         |    +--- org.codehaus.groovy:groovy:2.4.6 -> 2.4.11
|         |    \--- org.thymeleaf:thymeleaf:3.0.0.RELEASE -> 3.0.6.RELEASE (*)
|         +--- org.codehaus.groovy:groovy:2.4.6 -> 2.4.11
|         \--- org.thymeleaf:thymeleaf:3.0.0.RELEASE -> 3.0.6.RELEASE (*)

build.gradle の compile("org.springframework.boot:spring-boot-starter-thymeleaf") の記述を以下のように変更します。

dependencies {

    ..........

    compile("org.springframework.boot:spring-boot-starter-thymeleaf") {
        exclude group: "org.codehaus.groovy", module: "groovy"
    }

    ..........

}

変更した後、Gradle Tool Window の左上にある「Refresh all Gradle projects」ボタンをクリックして更新してから、clean タスク → Rebuild Project を実行するとエラーが出なくなります。また build タスクをすると “BUILD SUCCESSFUL” が出力されることも確認できます。

f:id:ksby:20170617160412p:plain

Java + JUnit4 ではなく Groovy + JUnit4 でテストを書いた時に便利な点とは

Groovy で書くのでいろいろシンプルになりますが、特に便利と思った点をメモしておきます。

Groovy では .class は不要

Groovy では .class を書く必要がありません。例えば @RunWith(Enclosed), @RunWith(SpringRunner) のように書けます。

@RunWith(Enclosed)
class SampleTest {

    @RunWith(SpringRunner)
    @SpringBootTest
    static class テストクラス {

        @Test
        void "Groovy + JUnit4 + Groovy SQL のテストサンプル1"() {

        }

    }

}

テストメソッド名を “” で囲んで自由に書ける

Java + JUnit4 では使えなかった ‘.’, ‘-’ や半角スペース等が自由に記述できます。

        @Test
        void "テストメソッド名に .,- () 等も使えます"() {

        }

テストメソッドに throws Exception の記述が不要

上下のサンプルを見ると分かるように、Java + JUnit4 の時にはテストメソッドに付けていた throws Exception を書く必要がなくなります。

Spcok でなくてもテストメソッド内で given:, when:, then:setup:, expect: が書ける(単なるコメントとしてだけのようですが)

given:, when:, then: の記述は Spock だから出来るものと思っていたのですが、Groovy + JUnit4 の場合でも問題なく書けます。

        @Test
        void "Groovy + JUnit4 + Groovy SQL のテストサンプル1"() {
            setup: "データを追加する"

            expect: "データが追加されているか確認する"
        }

Spock の時と同様に PowerAssert の分かりやすいレポートが出せる

※実例はこの後に書きます。

Groovy + JUnit4 + Groovy SQL のサンプルを書いてみる

以下のような処理をするテストを2パターン書きます。

  1. TestDataResource クラスが指定されたテーブルのバックアップを取得した後、用意されたデータをロードします。
  2. Groovy SQL でテーブルにデータを追加します。
  3. Groovy SQL でテーブルからデータを取得して検証します。
  4. TestDataResource クラスがバックアップのデータをリストアします。
package ksbysample.webapp.lending

import groovy.sql.Sql
import ksbysample.common.test.rule.db.BaseTestData
import ksbysample.common.test.rule.db.TestDataResource
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.experimental.runners.Enclosed
import org.junit.runner.RunWith
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.test.context.junit4.SpringRunner

import javax.sql.DataSource

@RunWith(Enclosed)
class SampleTest {

    // テストクラス内で共通で使用する定数を定義する
    class TestConst {
        static def TEST_ROLE_01 = "ROLE_USER"
        static def TEST_ROLE_02 = "ROLE_ADMIN"
    }

    @RunWith(SpringRunner)
    @SpringBootTest
    // @BaseTestData は TestDataResource クラスが使用する自作のアノテーション
    @BaseTestData("testdata/base")
    static class テストクラス {

        @Autowired
        private DataSource dataSource

        // 自作のテーブルのバックアップ・リストア用クラス
        @Rule
        @Autowired
        public TestDataResource testDataResource

        Sql sql

        @Before
        void setUp() {
            sql = new Sql(dataSource)
        }

        @After
        void tearDown() {
            sql.close()
        }

        @Test
        void "Groovy + JUnit4 + Groovy SQL のテストサンプル1"() {
            setup: "データを追加する"
            sql.execute("INSERT INTO user_role(role_id, user_id, role) VALUES (?, ?, ?)"
                    , [100, 1, TestConst.TEST_ROLE_01])

            expect: "データが追加されているか確認する"
            def row = sql.firstRow("SELECT * FROM user_role WHERE role_id = 100 AND user_id = 1")
            assert row.role == "ROLE_USER"
        }

        @Test
        void "Groovy + JUnit4 + Groovy SQL のテストサンプル2"() {
            setup: "データを3件追加する"
            sql.withBatch("INSERT INTO user_role(role_id, user_id, role) VALUES (?, ?, ?)") {
                it.addBatch([100, 6, TestConst.TEST_ROLE_01])
                it.addBatch([101, 7, TestConst.TEST_ROLE_01])
                it.addBatch([102, 7, TestConst.TEST_ROLE_02])
            }

            expect: "追加されたデータをチェックする(カラムは全て取得するが role カラムだけチェックする)"
            def rows = sql.rows("SELECT * FROM user_role WHERE user_id IN (6, 7) ORDER BY role_id")
            assert rows.role == ["ROLE_USER", "ROLE_USER", "ROLE_ADMIN"]

            and: "追加されたデータをチェックする(role_id, role の2カラムのみ取得してチェックする)"
            rows = sql.rows("""\
                SELECT
                  role_id,
                  role
                FROM user_role
                WHERE user_id IN (6, 7)
                ORDER BY role_id
            """)
            assert rows == [[role_id: 100, role: "ROLE_USER"]
                            , [role_id: 101, role: "ROLE_USER"]
                            , [role_id: 102, role: "ROLE_ADMIN"]]
        }

    }

}

テストを実行すると成功します。

f:id:ksby:20170617164346p:plain

ちなみに assert row.role == "ROLE_USER"assert row.role == "ROLE_xxx" に変更してテストを失敗させると、Spock の時と同様に PowerAssert の分かりやすいレポートが出力されます。

f:id:ksby:20170617165219p:plain

Groovy SQLIntelliJ IDEA + Groovy SQL で書くと便利な点とは

SQL 文に IntelliJ IDEA の SQL の Code Style が適用される

上のコードでは分かりませんが、IntelliJ IDEA 上だと下のキャプチャのように SQL 文に IntelliJ IDEA の Code Style が適用されて色付きで表示されます。

f:id:ksby:20170617172901p:plain

適用される Code Style の設定は IntelliJ IDEA の「Settings」ダイアログの中の「Editor」-「Code Style」-「SQL」で設定されているものです。

f:id:ksby:20170617173017p:plain

また Ctrl+Alt+L を押してコードフォーマットすると、一部だけですが適用されます。分かる範囲では、小文字で書いていた SQL 文が大文字に変更されましたが、自動で改行されたり不要なスペースが削除されたりはしませんでした。

例えば以下のように SQL 文を全て小文字で書いていて Ctrl+Alt+L を押すと、

f:id:ksby:20170617173801p:plain

以下のように大文字に変更されます。

f:id:ksby:20170617173910p:plain

なぜかメソッドの引数の SQL 文に、テーブル名、カラム名等の補完が効く

なんでこんなところで SQL 文の補完が有効になるの? と不思議になるくらい補完が効きます。ヒアドキュメントで書いている SQL 文でも有効でした。便利です!

f:id:ksby:20170617174152p:plain f:id:ksby:20170617174315p:plain f:id:ksby:20170617174557p:plain

SQL 文を書く時にヒアドキュメントが使える

Groovy で書いているので、文字列にヒアドキュメントが使えます。"""\ ... """を前後に付ければ改行を入れて SQL 文が記述できます。ただし Ctrl+Alt+L を押してもヒアドキュメントの文字列はフォーマットされないので注意が必要です。

f:id:ksby:20170617175057p:plain

最後に

Spock ではなく JUnit4 で書く場合でも Groovy が便利だし、Groovy SQL も便利すぎます。今後テストは基本 Groovy で書こうと思います。

履歴

2017/06/17
初版発行。
2017/06/21
* テストメソッドに @Test を付け忘れていたり、戻り値の型を void にしていない箇所を修正しました。
* テストメソッド名を "" で囲んで自由に書ける を追加しました。
* テストメソッドに throws Exception の記述が不要 を追加しました。