かんがるーさんの日記

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

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その39 )( Spring Boot を 1.5.7 → 1.5.9 へバージョンアップする )

概要

記事一覧はこちらです。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その38 )( IntelliJ IDEA から Jest のテストを実行する ) の続きです。

  • 今回の手順で確認できるのは以下の内容です。
    • Spring IO Platform の Brussels-SR6 がリリースされていることに気づきました。Spring Boot のバージョンを 1.5.7 → 1.5.9 へバージョンアップします。
    • 他にもバージョンアップしているライブラリを更新します。

参照したサイト・書籍

目次

  1. Spring Boot を 1.5.7 → 1.5.9 へバージョンアップする(他のライブラリもバージョンアップする)

手順

Spring Boot を 1.5.7 → 1.5.9 へバージョンアップする(他のライブラリもバージョンアップする)

build.gradle の以下の点を変更します。

group 'ksbysample'
version '1.0.0-RELEASE'

buildscript {
    ext {
        springBootVersion = '1.5.9.RELEASE'
    }
    repositories {
        mavenCentral()
        maven { url "https://plugins.gradle.org/m2/" }
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
        // for Error Prone ( http://errorprone.info/ )
        classpath("net.ltgt.gradle:gradle-errorprone-plugin:0.0.13")
    }
}

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'idea'
apply plugin: 'org.springframework.boot'
apply plugin: 'groovy'
apply plugin: 'net.ltgt.errorprone'
apply plugin: 'checkstyle'
apply plugin: 'findbugs'
apply plugin: 'pmd'

sourceCompatibility = 1.8
targetCompatibility = 1.8

task wrapper(type: Wrapper) {
    gradleVersion = '3.5'
}

[compileJava, compileTestGroovy, compileTestJava]*.options*.compilerArgs = ['-Xlint:all,-options,-processing,-path']
compileJava.options.compilerArgs += [
        '-Xep:RemoveUnusedImports:WARN'
        , '-Xep:NestedInstanceOfConditions:OFF'
        , '-Xep:InstanceOfAndCastMatchWrongType:OFF'
]

// for Doma 2
// JavaクラスとSQLファイルの出力先ディレクトリを同じにする
processResources.destinationDir = compileJava.destinationDir
// コンパイルより前にSQLファイルを出力先ディレクトリにコピーするために依存関係を逆転する
compileJava.dependsOn processResources

idea {
    module {
        inheritOutputDirs = false
        outputDir = file("$buildDir/classes/main/")
    }
}

configurations {
    // for Doma 2
    domaGenRuntime
}

checkstyle {
    configFile = file("${rootProject.projectDir}/config/checkstyle/google_checks.xml")
    toolVersion = '8.5'
    sourceSets = [project.sourceSets.main]
}

findbugs {
    toolVersion = '3.0.1'
    sourceSets = [project.sourceSets.main]
    ignoreFailures = true
    effort = "max"
    excludeFilter = file("${rootProject.projectDir}/config/findbugs/findbugs-exclude.xml")
}

tasks.withType(FindBugs) {
    // Gradle 3.3以降 + FindBugs Gradle Plugin を組み合わせると、"The following errors occurred during analysis:"
    // の後に "Cannot open codebase filesystem:..." というメッセージが大量に出力されるので、以下の doFirst { ... }
    // のコードを入れることで出力されないようにする
    doFirst {
        def fc = classes
        if (fc == null) {
            return
        }
        fc.exclude '**/*.properties'
        fc.exclude '**/*.sql'
        fc.exclude '**/*.xml'
        fc.exclude '**/META-INF/**'
        fc.exclude '**/static/**'
        fc.exclude '**/templates/**'
        classes = files(fc.files)
    }
    reports {
        xml.enabled = false
        html.enabled = true
    }
}

pmd {
    toolVersion = "5.8.1"
    sourceSets = [project.sourceSets.main]
    ignoreFailures = true
    consoleOutput = true
    ruleSetFiles = rootProject.files("/config/pmd/pmd-project-rulesets.xml")
    ruleSets = []
}

repositories {
    mavenCentral()
}

dependencyManagement {
    imports {
        // BOM は https://repo.spring.io/release/io/spring/platform/platform-bom/Brussels-SR6/
        // の下を見ること
        mavenBom("io.spring.platform:platform-bom:Brussels-SR6") {
            bomProperty 'guava.version', '22.0'
            bomProperty 'thymeleaf.version', '3.0.9.RELEASE'
            bomProperty 'thymeleaf-extras-springsecurity4.version', '3.0.2.RELEASE'
            bomProperty 'thymeleaf-layout-dialect.version', '2.2.2'
            bomProperty 'thymeleaf-extras-data-attribute.version', '2.0.1'
            bomProperty 'thymeleaf-extras-java8time.version', '3.0.1.RELEASE'
        }
    }
}

bootRepackage {
    mainClass = 'ksbysample.webapp.lending.Application'
}

dependencies {
    def spockVersion = "1.1-groovy-2.4"
    def domaVersion = "2.16.1"
    def lombokVersion = "1.16.18"
    def errorproneVersion = "2.1.3"
    def powermockVersion = "1.7.3"
    def seleniumVersion = "3.8.1"

    // dependency-management-plugin によりバージョン番号が自動で設定されるもの
    // Appendix A. Dependency versions ( http://docs.spring.io/platform/docs/current/reference/htmlsingle/#appendix-dependency-versions ) 参照
    compile("org.springframework.boot:spring-boot-starter-web")
    compile("org.springframework.boot:spring-boot-starter-thymeleaf") {
        exclude group: "org.codehaus.groovy", module: "groovy"
    }
    compile("org.springframework.boot:spring-boot-starter-data-jpa")
    compile("org.springframework.boot:spring-boot-starter-freemarker")
    compile("org.springframework.boot:spring-boot-starter-mail")
    compile("org.springframework.boot:spring-boot-starter-security")
    compile("org.springframework.boot:spring-boot-devtools")
    compile("org.springframework.session:spring-session")
    compile("com.google.guava:guava")
    compile("org.apache.commons:commons-lang3")
    compile("org.codehaus.janino:janino")
    testCompile("org.springframework.boot:spring-boot-starter-test")
    testCompile("org.springframework.security:spring-security-test")
    testCompile("org.yaml:snakeyaml")
    testCompile("org.mockito:mockito-core")

    // dependency-management-plugin によりバージョン番号が自動で設定されないもの、あるいは最新バージョンを指定したいもの
    compile("com.integralblue:log4jdbc-spring-boot-starter:1.0.2")
    compile("org.flywaydb:flyway-core:4.2.0")
    compile("com.h2database:h2:1.4.192")
    compile("com.github.rozidan:modelmapper-spring-boot-starter:1.0.0")
    testCompile("org.dbunit:dbunit:2.5.4")
    testCompile("com.icegreen:greenmail:1.5.5")
    testCompile("org.assertj:assertj-core:3.8.0")
    testCompile("org.spockframework:spock-core:${spockVersion}")
    testCompile("org.spockframework:spock-spring:${spockVersion}")
    testCompile("com.google.code.findbugs:jsr305:3.0.2")
    testCompile("org.jsoup:jsoup:1.11.2")

    // for lombok
    compileOnly("org.projectlombok:lombok:${lombokVersion}")
    testCompileOnly("org.projectlombok:lombok:${lombokVersion}")

    // for Doma
    compile("org.seasar.doma:doma:${domaVersion}")
    domaGenRuntime("org.seasar.doma:doma-gen:${domaVersion}")
    domaGenRuntime("com.h2database:h2:1.4.192")

    // for Error Prone ( http://errorprone.info/ )
    errorprone("com.google.errorprone:error_prone_core:${errorproneVersion}")
    compileOnly("com.google.errorprone:error_prone_annotations:${errorproneVersion}")

    // PowerMock
    testCompile("org.powermock:powermock-module-junit4:${powermockVersion}")
    testCompile("org.powermock:powermock-api-mockito:${powermockVersion}")

    // for Geb + Spock
    testCompile("org.gebish:geb-spock:2.0")
    testCompile("org.seleniumhq.selenium:selenium-chrome-driver:${seleniumVersion}")
    testCompile("org.seleniumhq.selenium:selenium-firefox-driver:${seleniumVersion}")
    testCompile("org.seleniumhq.selenium:selenium-support:${seleniumVersion}")
    testCompile("org.seleniumhq.selenium:selenium-api:${seleniumVersion}")
    testCompile("org.seleniumhq.selenium:selenium-remote-driver:${seleniumVersion}")
}

..........
  • buildscript の以下の点を変更します。
    • springBootVersion = '1.5.7.RELEASE'springBootVersion = '1.5.9.RELEASE' に変更します。
  • checkstyle の以下の点を変更します。
    • toolVersion = '8.3'toolVersion = '8.5' に変更します。
  • dependencyManagement の以下の点を変更します。
    • mavenBom("io.spring.platform:platform-bom:Brussels-SR5")mavenBom("io.spring.platform:platform-bom:Brussels-SR6") に変更します。
    • thymeleaf.version を 3.0.8.RELEASE3.0.9.RELEASE に変更します。
  • dependencies の以下の点を変更します。
    • def domaVersion = "2.16.1"def domaVersion = "2.19.0" に変更します。
    • def errorproneVersion = "2.1.1"def errorproneVersion = "2.1.3" に変更します。
    • def seleniumVersion = "3.6.0"def seleniumVersion = "3.8.1" に変更します。
    • testCompile("com.icegreen:greenmail:1.5.5")testCompile("com.icegreen:greenmail:1.5.6") に変更します。
    • testCompile("org.jsoup:jsoup:1.10.3")testCompile("org.jsoup:jsoup:1.11.2") に変更します。

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

clean タスク実行 → Rebuild Project 実行 → build タスクを実行してみますが、error-prone で エラー: An unhandled exception was thrown by the Error Prone static analysis plugin. というエラーが出ました。

f:id:ksby:20171206011656p:plain

コマンドプロンプトから gradlew --stacktrace build コマンドを実行してみると、今回は com.google.errorprone.bugpatterns.ParameterName.checkArguments で引っかかっていました。

f:id:ksby:20171206012140p:plain

build.gradle を修正し、-Xep:ParameterName:OFF オプションを追加します。

[compileJava, compileTestGroovy, compileTestJava]*.options*.compilerArgs = ['-Xlint:all,-options,-processing,-path']
compileJava.options.compilerArgs += [
        '-Xep:RemoveUnusedImports:WARN'
        , '-Xep:NestedInstanceOfConditions:OFF'
        , '-Xep:InstanceOfAndCastMatchWrongType:OFF'
        , '-Xep:ParameterName:OFF'
]

再び clean タスク実行 → Rebuild Project 実行 → build タスクを実行すると、今度は "BUILD SUCCESSFUL" が出力されました。

f:id:ksby:20171206012924p:plain

Project Tool Window で src/test/groovy/ksbysample を選択した後、コンテキストメニューを表示して「Run 'Tests in ksbysample' with Coverage」を選択し、テストが全て成功することも確認しておきます。

f:id:ksby:20171206021010p:plain

Tomcat を起動してから gebTest タスクを実行し Geb のテストも成功することを確認しようと思ったのですが、12 tests completed, 10 failed と一部のテストが失敗しました。

f:id:ksby:20171209001303p:plain

firefoxTest タスクと chromeTest タスクをそれぞれ個別に動かしてみましたが、どうも入力画面1→入力画面2へうまく遷移できていないようです。

f:id:ksby:20171209002201p:plain f:id:ksby:20171209002555p:plain

いろいろ調べた結果、テストが失敗するようになった原因は Spring Boot + npm + Geb で入力フォームを作ってテストする ( その35 )( Geb でテストを作成する2 ) で src/test/groovy/geb/module/FormModule.groovy に追加した $(it.key) << Keys.TAB でした。これを以下のようにコメントアウトしてから、

    void setValueList(valueList) {
        valueList.each {
            $(it.key).value(it.value)
//            $(it.key) << Keys.TAB
        }
    }

firefoxTest タスクを実行するとテストは全て成功します。

f:id:ksby:20171209084055p:plain

どうも $(it.key) << Keys.TAB を実行した後だと、単に $("#inquiryInput01Form").sex = "1" と書いただけでは値がセットできないようです。$(it.key).value(it.value) のように Navigator#value メソッドを使用すればセットできたので、これを使用する方法に変更します。

まずは src/test/groovy/geb/module/FormModule.groovy を以下のように変更します。

package geb.module

import geb.Module
import org.openqa.selenium.Keys
import org.openqa.selenium.WebElement

/**
 * フォーム共通の content や、テストに使用するメソッドを定義するクラス
 */
class FormModule extends Module {

    static content = {
        btnBack { $(".js-btn-back") }
        btnNext { $(".js-btn-next") }
    }

    /**
     * Form の入力項目に値をセットする
     * @param selector セットする要素のセレクタ
     * @param value セットする値
     */
    void setValue(selector, value) {
        $(selector).value(value)
        $(selector) << Keys.TAB
    }

    /**
     * Form の入力項目に値を一括セットする
     * valueList は以下の形式の Map である
     * <pre>{@code
     * static initialValueList = [
     *      "#lastname"        : "",
     *      "#firstname"       : "",
     *      "#lastkana"        : "",
     *      "#firstkana"       : "",
     *      "input[name='sex']": null,
     *      "#age"             : "",
     *      "#job"             : ""
     * ]
     *}</pre>
     *
     * @param valueList セットするセレクタと値を記述した Map
     */
    void setValueList(valueList) {
        valueList.each {
            setValue(it.key, it.value)
        }
    }

    ..........

}
  • setValue メソッドを追加します。
  • setValueList メソッド内の値をセットする処理を setValue メソッドを呼び出すように変更します。

次に src/test/groovy/geb/gebspec/inquiry/InquiryTestSpec.groovy 内で入力項目に値をセットする処理を form.setValue メソッドを使用するように変更します。

package geb.gebspec.inquiry

import geb.page.inquiry.InquiryInput01Page
import geb.page.inquiry.InquiryInput02Page
import geb.spock.GebSpec
import org.openqa.selenium.Keys
import org.openqa.selenium.WebElement
import spock.lang.Unroll

class InquiryTestSpec extends GebSpec {

    def "入力画面1の画面初期表示時に想定している値がセットされている"() {
        setup: "入力画面1を表示する"
        to InquiryInput01Page

        expect: "初期値が表示されている"
        form.assertValueList(initialValueList)
    }

    def "入力画面1の各入力項目に最大文字数を入力できる"() {
        setup: "入力画面1を表示し入力項目に最大文字数の文字を入力する"
        to InquiryInput01Page
        form.setValueList(maxLengthValueList)

        expect: "入力した最大文字数の文字が入力されている"
        form.assertValueList(maxLengthValueList)
    }

    def "入力画面1に入力後、入力画面2へ遷移→入力画面1へ戻ると入力した値が表示される"() {
        given: "入力画面1を表示する"
        to InquiryInput01Page

        when: "最大文字数の文字を入力して次へボタンをクリックする"
        form.setValueList(maxLengthValueList)
        form.setValue("input[name='sex']", "1")
        form.btnNext.click(InquiryInput02Page)

        then: "入力画面2へ遷移し初期値が表示されている"
        form.assertValueList(initialValueList)

        and: "戻るボタンをクリックする"
        form.btnBack.click(InquiryInput01Page)

        then: "入力した最大文字数の文字が入力されている"
        form.assertValueList(maxLengthValueList)
        $("#inquiryInput01Form").sex == "1"
    }

    def "入力画面2の各入力項目に最大文字数を入力できる"() {
        setup: "入力画面1を表示して最大文字数の文字を入力してから次へボタンをクリックする"
        to InquiryInput01Page
        form.setValueList(maxLengthValueList)
        form.setValue("input[name='sex']", "1")
        form.btnNext.click(InquiryInput02Page)

        and: "入力画面2で最大文字数の文字を入力する"
        form.setValueList(maxLengthValueList)

        expect: "入力した最大文字数の文字が入力されている"
        form.assertValueList(maxLengthValueList)
    }

    def "入力画面2で郵便番号を入力してautocompleteで表示された住所を選択する"() {
        given: "入力画面1から入力画面2へ遷移する"
        to InquiryInput01Page
        form.setValueList(maxLengthValueList)
        form.setValue("input[name='sex']", "1")
        form.btnNext.click(InquiryInput02Page)

        when: "郵便番号を入力する"
        $("#inquiryInput02Form").zipcode1 = "100"
        $("#inquiryInput02Form").zipcode2 = "0005" << Keys.TAB

        and: "autocomplete のドロップダウンメニューが表示されたら最初の選択肢を選択する"
        waitFor(5) { $(".ui-autocomplete .ui-menu-item") }
        List<WebElement> elementList = $(".ui-autocomplete .ui-menu-item > div").allElements()
        elementList.first().click()

        then: "住所にクリックした選択肢がセットされている"
        $("#inquiryInput02Form").address == "東京都千代田区丸の内"
    }

    @Unroll
    def "入力画面2の電話番号とメールアドレスの組み合わせテスト(#tel1,#tel2,#tel3,#email)"() {
        given: "入力画面1から入力画面2へ遷移する"
        to InquiryInput01Page
        form.setValueList(maxLengthValueList)
        form.setValue("input[name='sex']", "1")
        form.btnNext.click(InquiryInput02Page)

        when: "電話番号と郵便番号を入力する"
        form.setValue("#tel1", tel1)
        form.setValue("#tel2", tel2)
        form.setValue("#tel3", tel3)
        form.setValue("#email", email)

        then: "エラーメッセージの表示状況をチェックする"
        $("#form-group-tel .js-errmsg").text() == telErrMsg
        $("#form-group-email .js-errmsg").text() == emailErrMsg

        where:
        tel1 | tel2   | tel3   | email                 || telErrMsg                            | emailErrMsg
        "03" | "1234" | "5678" | "tanaka@sample.co.jp" || ""                                   | ""
        "03" | "1234" | "5678" | ""                    || ""                                   | ""
        ""   | ""     | ""     | "tanaka@sample.co.jp" || ""                                   | ""
        ""   | ""     | ""     | ""                    || "電話番号とメールアドレスのいずれか一方を入力してください"       | "電話番号とメールアドレスのいずれか一方を入力してください"
        "3"  | "1234" | "5678" | ""                    || "市外局番の先頭には 0 の数字を入力してください"           | ""
        "03" | "123"  | "5678" | ""                    || "市外局番+市内局番の組み合わせが数字6桁になるように入力してください" | ""
        "03" | "1234" | "567"  | ""                    || "加入者番号には4桁の数字を入力してください"              | ""
    }

}

再び gebTest タスクを実行すると、テストが全て成功するようになりました。

f:id:ksby:20171209094511p:plain

履歴

2017/12/09
初版発行。