かんがるーさんの日記

最近自分が興味をもったものを調べた時の手順等を書いています。今は 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
初版発行。

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

概要

記事一覧はこちらです。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その37 )( Jest で jQuery を利用したモジュールのテストを書く ) の続きです。

参照したサイト・書籍

目次

  1. IntelliJ IDEA のエディタの左側に表示されるアイコンから Jest のテストを実行する
    1. エディタの左側に表示されるアイコンから converter.test.js を実行してみる
    2. 「Run/Debug Configuraitons」ダイアログで Jest の設定をする
    3. エディタの左側に表示されるアイコンから validator.test.js を実行する
    4. 全ての Jest のテストを実行する
  2. npm ウィンドウから npm scripts を実行する
    1. npm ウィンドウを表示して npm test コマンドを実行する
    2. 「Run/Debug Configuraitons」ダイアログで npm の設定をする
    3. npm ウィンドウから npm run springboot コマンドを実行する
  3. メモ書き
    1. JSDoc を書いておくと Ctrl+P を押した時に引数の型が表示される
  4. 次回は。。。

手順

IntelliJ IDEA のエディタの左側に表示されるアイコンから Jest のテストを実行する

エディタの左側に表示されるアイコンから converter.test.js を実行してみる

まずはエディタの左側に表示されているアイコンをクリックしてみます。JRebel のメニューまで出ているのが少し気になりますがここは無視します。Run 'converter.js のテスト' を選択します。

f:id:ksby:20171202194711p:plain

「Edit configuration」ダイアログが表示されたので、以下の画像のように設定します。

f:id:ksby:20171202200012p:plain

  • 「Configuration file」に C:\project-springboot\ksbysample-boot-miscellaneous\boot-npm-geb-sample\jest.config.json を設定します。
  • 「Node interpreter」に C:\nodejs\node.exe が赤字で表示されていましたが、Nodist でインストールしているので C:\Nodist\bin\node.exe に変更します。
  • 「Jest options」に --coverage を設定します。

設定後、画面下の「Run」ボタンをクリックすると、画面下に Java のテストの時と同じウィンドウが表示されてテストの実行結果が表示されました。

f:id:ksby:20171202200333p:plain

コードカバレッジの部分がずれて表示されていますが、このままにします(エディタと同じ Source Code Pro 12pt にしたら表がずれずに表示されたのですが Tomcat 起動時のログの文字サイズも大きくなって見にくくなったので止めました)。

「Run/Debug Configuraitons」ダイアログで Jest の設定をする

Run/Debug Configuraitons の Jest の Default 設定を変更して、エディタの左側から実行しようとした時に都度ダイアログで設定しなくてもよいようにします。

IntelliJ IDEA のメインメニューから「Run」-「Edit Configurations...」を選択します。

「Run/Debug Configuraitons」ダイアログが表示されるので、画面左側から「Defaults」-「Jest」を選択し、画面右側を以下の画像のように設定します。

f:id:ksby:20171202205141p:plain

  • 「Configuration file」に C:\project-springboot\ksbysample-boot-miscellaneous\boot-npm-geb-sample\jest.config.json を設定します。
  • 「Node interpreter」に C:\nodejs\node.exe が赤字で表示されていますので、C:\Nodist\bin\node.exe に変更します。
  • 「Working directory」に C:\project-springboot\ksbysample-boot-miscellaneous\boot-npm-geb-sample\src\test を設定します。
  • 「Jest options」に --coverage を設定します。

画面下の「Apply」ボタンをクリックして反映します。

次に全ての Jest のテストを実行するための設定を追加します。左側のツリーの上の方に先程実行したテストの設定が「Jest」-「converter.js のテスト」として登録されているので、それを選択します。選択したら画面右側を以下の画像のように変更します。

f:id:ksby:20171202210952p:plain

  • 「Name」を converter.js のテストall Jest test に変更します。
  • 画面中央のラジオボタンの選択を「Suite」→「All tests」に変更します。

「OK」ボタンをクリックしてダイアログを閉じます。

エディタの左側に表示されるアイコンから validator.test.js を実行する

validator.test.js を開いているエディタの左側のアイコンからテストを実行して、ダイアログが表示されないことを確認します。

「Run 'validator.js のテスト'」を選択すると、

f:id:ksby:20171202232027p:plain

ダイアログが表示されずにテストが実行されました。

f:id:ksby:20171202232208p:plain

全ての Jest のテストを実行する

画面右上のドロップダウンリストから「all Jest test」を選択した後、

f:id:ksby:20171202232648p:plain

隣の Run ボタンをクリックします。

f:id:ksby:20171202232855p:plain

下にウィンドウが開いて全ての Jest のテストが実行されました。

f:id:ksby:20171202233111p:plain

npm ウィンドウから npm scripts を実行する

npm ウィンドウを表示して npm test コマンドを実行する

Project ウィンドウ内で package.json を選択→右クリックしてコンテキストメニューを表示した後、「Show npm Scripts」を選択します。

f:id:ksby:20171202235211p:plain

Project ウィンドウの下に npm ウィンドウが表示されます。

f:id:ksby:20171202235511p:plain

test を選択して Enter キーを押すと「Edit configuration」ダイアログが表示されますので、以下の画像のように設定します。

f:id:ksby:20171203220015p:plain

  • 「Node interpreter」を C:\nodejs\node.exeC:\Nodist\bin\node.exe に変更します。

設定後、画面の下の「Run」ボタンをクリックします。画面の下にウィンドウが開き npm test コマンドが実行されました。

f:id:ksby:20171203220206p:plain

「Run/Debug Configuraitons」ダイアログで npm の設定をする

Run/Debug Configuraitons の npm の Default 設定を変更して、npm ウィンドウから npm scripts を実行する時に都度ダイアログで設定しなくてもよいようにします。

IntelliJ IDEA のメインメニューから「Run」-「Edit Configurations...」を選択します。

「Run/Debug Configuraitons」ダイアログが表示されるので、画面左側から「Defaults」-「npm」を選択し、画面右側を以下の画像のように設定します。

f:id:ksby:20171203224214p:plain

  • 「package.json」に C:\project-springboot\ksbysample-boot-miscellaneous\boot-npm-geb-sample\package.json を設定します。
  • 「Node interpreter」を C:\nodejs\node.exeC:\Nodist\bin\node.exe に変更します。

「OK」ボタンをクリックしてダイアログを閉じます。

npm ウィンドウから npm run springboot コマンドを実行する

npm ウィンドウを開き springboot を選択して実行します。

f:id:ksby:20171203224520p:plain

今度はダイアログは表示されず npm run springboot コマンドが実行されました。

f:id:ksby:20171203224745p:plain

メモ書き

JSDoc を書いておくと Ctrl+P を押した時に引数の型が表示される

IntelliJ IDEA のエディタで Javascript の関数にカーソルを移動して Ctrl+P を押すと引数を表示してくれますが、

f:id:ksby:20171205011740p:plain

JSDoc を書いていると引数名だけでなく型も表示してくれます。

f:id:ksby:20171205011941p:plain

また引数にカーソルを当てると、カーソルが当たっている引数の部分が強調表示されます。

f:id:ksby:20171205012148p:plain

次回は。。。

引き続き Jest を利用して Form.js や validator.js のテストを書きます。またいくつか間違いや修正した方が良さそうな点も見つけたので修正する予定です。

履歴

2017/12/05
初版発行。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その37 )( Jest で jQuery を利用したモジュールのテストを書く )

概要

記事一覧はこちらです。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その36 )( Node.js を 6.11.1 → 8.9.1 へ、npm を 4.0.5 → 5.5.1 へバージョンアップする ) の続きです。

  • 今回の手順で確認できるのは以下の内容です。
    • Javascript のテストを作成するのにテストフレームワークは何を使えばよいのか調べていたのですが、どうも最近は Jest がいいらしいです。Mocha だと他のライブラリと組み合わせて使う必要がありますが、Jest だとオールインワンなのでセットアップが楽で、これをインストールするだけでモックやコードカバレッジも利用できるようになるとのこと。
    • Jest は Facebook 製で React.js のプロジェクトのテストに一番向いているようですが、jQuery のテストを書くことも出来るようなので、試しに書いてみます。

参照したサイト・書籍

  1. Jest
    https://facebook.github.io/jest/

  2. Facebook製のJavaScriptテストツール「Jest」の逆引き使用例
    https://qiita.com/chimame/items/e97883fd46b67529d59f

  3. React + jestでテスト実行時にだけjQueryを読み込む(jquery-rails向け)
    https://qiita.com/foloinfo/items/40e04252dc5ea3638031

  4. Jestを使ってみてのハマりどころメモ
    http://lealog.hateblo.jp/entry/2017/11/16/172815

  5. DOM Manipulation
    https://facebook.github.io/jest/docs/en/tutorial-jquery.html

  6. JavaScriptのプログラミングはこれだけ効率化できる!使用歴5年目のエンジニアが送るWebStormの厳選神業集
    https://ics.media/entry/16760

    • 今回の記事とは関係ありませんが、WebStorm で Javascript を実装する際に効率化する方法がまとまった記事が出ていたのでメモしておきます。
  7. @use JSDoc
    http://usejsdoc.org/index.html

    • こちらも今回の記事とは関係ありませんが、JSDoc の Webサイトを見つけたのでメモしておきます。

目次

  1. Jest をインストールする
  2. IntelliJ IDEA の Javascript language version の設定を ECMAScript 5.1 → ECMAScript 6 に変更する
  3. 簡単なテストを作成してみる
  4. converter.js のテストを書いてみる
  5. コードカバレッジを取得する
  6. モックを定義して validator.js のテストを書いてみる

手順

Jest をインストールする

npm install --save-dev jest コマンドを実行して Jest をインストールします。

f:id:ksby:20171129011318p:plain

package.json を修正して npm test コマンドで jest が実行されるようにします。

  "scripts": {
    "test": "jest",
    ..........
  • "test": "echo \"Error: no test specified\" && exit 1""test": "jest" に変更します。

テストがない状態で npm test コマンドを実行してみると、以下の画像の結果が出力されます。

f:id:ksby:20171129015033p:plain

これを見ると、__tests__ ディレクトリ(階層はルート直下でなくてもよい)の下の ~spec.js(x) あるいは ~test.js(x) というファイルを作成しておけば、そのファイルのテストが実行されるようです。

js のソースは src/main/assets ディレクトリの下に作成したので、テストは src/test の下に asserts/__tests__ ディレクトリを作成し、その下にテストの js ファイルを作成するようにします。

f:id:ksby:20171129024108p:plain

IntelliJ IDEA の Javascript language version の設定を ECMAScript 5.1 → ECMAScript 6 に変更する

Jest のテストは Node.js の Javascript エンジン「V8」上で動かすので、ES2015 で書くことにします。

IntelliJ IDEA のメインメニューから「File」-「Settings...」を選択して「Settings」ダイアログを表示した後、左側のメニューから「Language & Frameworks」-「Javascript」を選択します。

画面右側の「Javascript language version」で「ECMAScript 6」を選択します。

f:id:ksby:20171201004143p:plain

簡単なテストを作成してみる

1つのファイルだけでテストするサンプルを作成してみます。

// 別ファイルの jQuery を利用したモジュールをテストする場合に var $ = require("jquery") だと
// エラーになるので global.$ にセットする
global.$ = require("jquery");

describe("テストのサンプルその1", () => {

    const setError = function (id) {
        $(id).addClass("has-error")
    };

    const sampleBlurEventHandler = function (event) {
        $("#sample").val("length = " + $("#sample").val().length);
    };

    beforeEach(() => {
        // HTML を書く時は ES2015 のテンプレート文字列を使うと楽
        document.body.innerHTML = `
            <div class="form-group" id="form-group-sample">
              <div class="control-label col-sm-2">
                <label class="float-label">サンプル</label>
              </div>
              <div class="col-sm-10">
                <div class="row">
                  <div class="col-sm-10">
                    <input type="text" name="sample" id="sample" class="form-control form-control-inline"
                           maxlength="20" value="" placeholder="例)サンプル"/>
                  </div>
                </div>
                <div class="row hidden js-errmsg">
                  <div class="col-sm-10">
                    <p class="form-control-static text-danger">
                      <small>ここにエラーメッセージを表示します</small>
                    </p>
                  </div>
                </div>
              </div>
            </div>
        `;
    });

    test("setError関数を呼ぶと指定されたidのelementにhas-errorクラスが追加される", () => {
        expect($("#form-group-sample").prop("class")).not.toContain("has-error");
        setError("#form-group-sample");
        expect($("#form-group-sample").prop("class")).toContain("has-error");
    });

    test("blurイベントが発生すると値が'length = [入力された文字列の長さ]'に変わる", () => {
        const str = "これはテストです。";
        $("#sample").on("blur", sampleBlurEventHandler);
        $("#sample").val(str);
        $("#sample").blur();
        expect($("#sample").val()).toBe("length = " + str.length)
    });

});

npm test コマンドを実行すると、テストが成功することが確認できます。

f:id:ksby:20171201071900p:plain

ちなみに test("setError関数を呼ぶと指定されたidのelementにhas-errorクラスが追加される", ... の方のテストが失敗するように以下のように修正してから、

    test("setError関数を呼ぶと指定されたidのelementにhas-errorクラスが追加される", () => {
        expect($("#form-group-sample").prop("class")).not.toContain("has-error");
        setError("#form-group-sample");
        // expect($("#form-group-sample").prop("class")).toContain("has-error");
        expect($("#form-group-sample").prop("class")).toContain("has-success");
    });

npm test コマンドを実行すると、テストは全て実行されますが、修正したテストが失敗することが確認できます。

f:id:ksby:20171201072122p:plain

修正したテストは元に戻します。

converter.js のテストを書いてみる

まず src/main/assets/js の下のモジュールを require で読み込めるよう設定ファイルを追加します。ルートディレクトリ直下に jest.config.json というファイルを新規作成し、以下の内容を記述します。

{
  "moduleDirectories": [
    "node_modules",
    "src/main/assets/js"
  ]
}

設定ファイルは js 形式(jest.config.js)でも書けますが、JSON 形式と js 形式では以下の違いがありました。後で(次の記事の予定)IntelliJ IDEA の Run/Debug Configurations で Jest の設定をしてテストを実行する方法をまとめるので、今回は JSON 形式にします。

  • JSON 形式
    • npm scripts で --config=... で設定ファイルを指定する必要がある。
    • IntelliJ IDEA の Run/Debug Configurations で Jest の設定をして実行することができる。
  • js 形式
    • npm scripts で --config=... で設定ファイルを指定しなくてもよい(自動で認識される)。
    • IntelliJ IDEA の Run/Debug Configurations で Jest の設定をして実行することができない(JSON形式しか指定できない)。

次に package.json を以下のように変更します。

  "scripts": {
    "test": "jest --config=jest.config.json",
    ..........
  • --config=jest.config.json を追加します。

テストを書きます。src/test/assets/tests の下に lib/util ディレクトリを作成します。

src/main/assets/tests/lib/util の下に converter.test.js を新規作成し、以下の内容を記述します。

"use strict";

global.$ = require("jquery");
const converter = require("lib/util/converter.js");

describe("converter.js のテスト", () => {

    describe("convertHiragana のテスト", () => {
        beforeEach(() => {
            document.body.innerHTML = `
              <input type="text" name="sample" id="sample" value=""/>
            `;
        });

        test("全角カタカナはひらがなに変更される", () => {
            $("#sample").val("アイウエオ");
            converter.convertHiragana(["#sample"]);
            expect($("#sample").val()).toBe("あいうえお");
        });

        test("半角カタカナはひらがなに変更される", () => {
            $("#sample").val("アイウエオ");
            converter.convertHiragana(["#sample"]);
            expect($("#sample").val()).toBe("あいうえお");
        });
    });

    describe("convertHanAlphaNumeric のテスト", () => {
        beforeEach(() => {
            document.body.innerHTML = `
              <input type="text" name="sample" id="sample" value=""/>
            `;
        });

        test("全角英数字は半角に変更される", () => {
            $("#sample").val("AZaz09");
            converter.convertHanAlphaNumeric(["#sample"]);
            expect($("#sample").val()).toBe("AZaz09");
        });
    });

});

npm test コマンドを実行するとテストが成功しました。

f:id:ksby:20171202081253p:plain

コードカバレッジを取得する

まずレポートファイルが出力するディレクトリを jest.config.json に設定します。設定しない場合、プロジェクトのルートディレクトリ直下に coverage ディレクトリが作成されて、その中に出力されます。Javacheckstyle 等のツールは /build/reports ディレクトリの下にレポートファイルを出力しているので、/build/reports/jest ディレクトリの下に出力されるようにします。

jest.config.json を以下のように変更します。

{
  "coverageDirectory": "build/reports/jest",
  "moduleDirectories": [
    "node_modules",
    "src/main/assets/js"
  ]
}
  • "coverageDirectory": "build/reports/jest" を追加します。

コードカバレッジを取得するよう npm scripts を変更します。package.json を以下のように変更します。

  "scripts": {
    "test": "jest --config=jest.config.json --coverage",
    ..........
  • --coverage を追加します。

npm test コマンドを実行してみます。今度はテストが実行されたモジュールのコードカバレッジの状況も出力されました。

f:id:ksby:20171202083043p:plain

レポートファイルを見てみます。build/reports/jest の下には以下のようなファイルが出力されています。

f:id:ksby:20171202083300p:plain

build/reports/jest/lcov-report/index.html を開いてみます。

f:id:ksby:20171202083439p:plain

converter.js がリンクになっているのでクリックしてみると、コードのどの部分が何回呼び出されたのかが分かります。

f:id:ksby:20171202083636p:plain f:id:ksby:20171202083932p:plain

このレポートファイルは見やすくていいですね。

モックを定義して validator.js のテストを書いてみる

src/test/assets/tests/lib/util の下に validator.test.js を新規作成し、以下の内容を記述します。

"use strict";

global.$ = require("jquery");
const validator = require("lib/util/validator.js");
// Form クラスをモックにする
jest.mock("lib/class/Form.js");
const Form = require("lib/class/Form.js");

describe("validator.js のテスト", () => {

    describe("checkRequired のテスト", () => {
        test("form.isAnyEmpty = false なら form.setSuccess が呼び出される", () => {
            // form.isAnyEmpty をモックメソッドにして常に false を返すようにする
            var form = new Form([]);
            form.isAnyEmpty = jest.fn().mockImplementation(() => false);

            // validator.checkRequired メソッドを呼び出す
            validator.checkRequired(form, ["#form-group-sample"], ["#sample"], "");

            // form.setSuccess メソッドが呼び出され、form.setError メソッドが呼び出されていないことを確認する
            expect(form.setSuccess).toBeCalled();
            expect(form.setError).not.toBeCalled();
        });

        test("form.isAnyEmpty = true なら form.setError が呼び出される", () => {
            // form.isAnyEmpty をモックメソッドにして常に true を返すようにする
            var form = new Form([]);
            form.isAnyEmpty = jest.fn().mockImplementation(() => true);

            // validator.checkRequired メソッドを呼び出す
            // Error オブジェクトが throw されるはず
            const errmsg = "エラーメッセージ";
            expect(() => {
                validator.checkRequired(form, ["#form-group-sample"], ["#sample"], errmsg);
            }).toThrow(new Error(errmsg));

            // form.setSuccess メソッドは呼び出されず、form.setError メソッドが呼び出されることを確認する
            expect(form.setSuccess).not.toBeCalled();
            expect(form.setError).toBeCalled();
        });
    });

});

npm test コマンドを実行します。テストは全て成功しますが、コードカバレッジが低いと赤色や黄色の文字で表示されます。

f:id:ksby:20171202161251p:plain

レポートファイルを開いてみます。build/reports/jest/lcov-report/index.html を開くと、今度はディレクトリ単位でコードカバレッジが表示され、

f:id:ksby:20171202161517p:plain

class のリンクをクリックしていくと、

f:id:ksby:20171202161627p:plain f:id:ksby:20171202161735p:plain

Form.js が表示され、実行されていない箇所が赤く表示されます。

履歴

2017/12/02
初版発行。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その36 )( Node.js を 6.11.1 → 8.9.1 へ、npm を 4.0.5 → 5.5.1 へバージョンアップする )

概要

記事一覧はこちらです。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その35 )( Geb でテストを作成する2 ) の続きです。

  • 今回の手順で確認できるのは以下の内容です。
    • https://nodejs.org/ja/ を見ると Node.js の推奨版が 8.9.1 LTS になっていたので、バージョンアップします。
    • npm も 5.5.1 へバージョンアップし、5.2.0 以降で使えるようになった npx コマンドを試してみます。

参照したサイト・書籍

  1. Node8の注目的変更まとめ
    http://abouthiroppy.hatenablog.jp/entry/2017/05/30/090015

  2. npm 5.2.0の新機能! 「npx」でローカルパッケージを手軽に実行しよう
    https://qiita.com/tonkotsuboy_com/items/8227f5993769c3df533d

  3. nodistでNode.jsのバージョン管理
    https://qiita.com/hitomatagi/items/ad5f28ccd095b962f8ba

目次

  1. Node.js を 6.11.1 → 8.9.1 へ、npm を 4.0.5 → 5.5.1 へバージョンアップする
  2. npx コマンドを使用してみる+package.json を変更する

手順

Node.js を 6.11.1 → 8.9.1 へ、npm を 4.0.5 → 5.5.1 へバージョンアップする

nodist dist コマンドを実行して 8.9.1 がインストール可能か確認します。

f:id:ksby:20171126021453p:plain f:id:ksby:20171126021616p:plain

8.9.1 が表示されていますので、8.9.1 へバージョンアップします。

f:id:ksby:20171126025112p:plain

npm も最新版にバージョンアップします。npm の最新バージョンは https://docs.npmjs.com/ の一番下を見ると 5.5.1 とありましたので、

f:id:ksby:20171126025857p:plain

5.5.1 にバージョンアップします。

f:id:ksby:20171126030121p:plain

npm run springboot コマンドを実行すると特にエラーが出ずに各 npm-scripts も実行されました。

f:id:ksby:20171126030604p:plain f:id:ksby:20171126030727p:plain

npx コマンドを使用してみる+package.json を変更する

npm のバージョンを 5.2.0 以降にすれば npx コマンドが使えるとのことですが、npx コマンドを実行しようとしてもコマンドがありませんでした。

f:id:ksby:20171127004300p:plain

nodist で npm をバージョンアップすると npm 自体のバージョンは上がりますが、npx が入っていないようです。npm install -g npm@5.5.1 でバージョンアップしてみます。

f:id:ksby:20171127005226p:plain f:id:ksby:20171127005352p:plain

今度は npx コマンドが使えるようになりました。

npx コマンドで直接パッケージを呼び出せるようになりましたので、package.json から不要な npm-scripts を削除します。

  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "postinstall": "run-s clean:static-dir copy:all",
    "clean:static-dir": "rimraf src/main/resources/static/*",
    "copy:all": "run-p copy:bootstrap copy:admin-lte copy:font-awesome copy:ionicons copy:jquery-ui-css",
    "copy:bootstrap": "cpx node_modules/bootstrap/dist/**/* src/main/resources/static/vendor/bootstrap",
    "copy:admin-lte": "cpx node_modules/admin-lte/dist/**/* src/main/resources/static/vendor/admin-lte",
    "copy:font-awesome": "cpx node_modules/font-awesome/{css,fonts}/**/* src/main/resources/static/vendor/font-awesome",
    "copy:ionicons": "cpx node_modules/ionicons/dist/{css,fonts}/**/* src/main/resources/static/vendor/ionicons",
    "copy:jquery-ui-css": "cpx node_modules/jquery-ui/themes/base/**/* src/main/resources/static/vendor/jquery-ui/css",
    "postcss:watch": "postcss src/main/assets/css/**/* -d src/main/resources/static/css -x .min.css -w --poll",
    "webpack:watch": "webpack --watch",
    "browser-sync:start": "browser-sync start --config bs-config.js",
    "browser-sync:springboot": "browser-sync start --config bs-springboot-config.js",
    "server": "run-p postcss:watch webpack:watch browser-sync:start",
    "springboot": "run-p postcss:watch webpack:watch browser-sync:springboot"
  },
  • 以下の2行を削除します。
    • "webpack": "webpack"
    • "browser-sync": "browser-sync"

npx でパッケージが直接呼び出せるようになりましたので、npx webpack -v を実行すれば webpack のバージョンが表示されます。

f:id:ksby:20171127010323p:plain

履歴

2017/11/27
初版発行。

IntelliJ IDEA を 2017.2.5 → 2017.2.6 へ、Git for Windows を 2.14.2(2) → 2.15.0 へバージョンアップ

IntelliJ IDEA を 2017.2.5 → 2017.2.6 へバージョンアップする

IntelliJ IDEA の 2017.2.6 がリリースされたのでバージョンアップします。

※ksbysample-webapp-lending プロジェクトを開いた状態でバージョンアップしています。

  1. IntelliJ IDEA のメインメニューから「Help」-「Check for Updates...」を選択します。

  2. IDE and Plugin Updates」ダイアログが表示されます。左下に「Update and Restart」ボタンが表示されていますので、「Update and Restart」ボタンをクリックします。

    f:id:ksby:20171124010325p:plain

  3. Plugin の update も表示されました。「Error-prone Compiler Integration」は後でアンインストールするので(Error-prone は gradle の build タスクを実行する時だけ動けばよさそうなので)、これだけチェックを外して「Update and Restart」ボタンをクリックします。

    f:id:ksby:20171124010508p:plain

  4. Patch がダウンロードされて IntelliJ IDEA が再起動します。

  5. IntelliJ IDEA が起動すると画面下部に「Indexing…」のメッセージが表示されますので、終了するまで待機します。

    f:id:ksby:20171124012408p:plain

  6. IntelliJ IDEA のメインメニューから「Help」-「About」を選択し、2017.2.6 へバージョンアップされていることを確認します。

  7. Gradle Tool Window のツリーを見ると「Tasks」の下に「other」しかない状態になっているので、左上にある「Refresh all Gradle projects」ボタンをクリックして更新します。

    f:id:ksby:20171124012557p:plain

  8. clean タスク実行 → Rebuild Project 実行 → build タスクを実行して、"BUILD SUCCESSFUL" のメッセージが出力されることを確認します。

    f:id:ksby:20171124013230p:plain

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

    f:id:ksby:20171124013726p:plain

Git for Windows を 2.14.2(2) → 2.15.0 へバージョンアップする

Git for Windows の 2.15.0 がリリースされていたのでバージョンアップします。

  1. https://git-for-windows.github.io/ の「Download」ボタンをクリックして Git-2.15.0-64-bit.exe をダウンロードします。

  2. Git-2.15.0-64-bit.exe を実行します。

  3. 「Git 2.15.0 Setup」ダイアログが表示されます。[Next >]ボタンをクリックします。

  4. 「Select Components」画面が表示されます。「Git LFS(Large File Support)」だけチェックした状態で [Next >]ボタンをクリックします。

  5. 「Adjusting your PATH environment」画面が表示されます。中央の「Use Git from the Windows Command Prompt」が選択されていることを確認後、[Next >]ボタンをクリックします。

  6. 「Choosing HTTPS transport backend」画面が表示されます。「Use the OpenSSL library」が選択されていることを確認後、[Next >]ボタンをクリックします。

  7. 「Configuring the line ending conversions」画面が表示されます。一番上の「Checkout Windows-style, commit Unix-style line endings」が選択されていることを確認した後、[Next >]ボタンをクリックします。

  8. 「Configuring the terminal emulator to use with Git Bash」画面が表示されます。「Use Windows'default console window」が選択されていることを確認した後、[Next >]ボタンをクリックします。

  9. 「Configuring extra options」画面が表示されます。「Enable file system caching」だけがチェックされていることを確認した後、[Install]ボタンをクリックします。

  10. インストールが完了すると「Completing the Git Setup Wizard」のメッセージが表示された画面が表示されます。中央の「View Release Notes」のチェックを外した後、「Finish」ボタンをクリックしてインストーラーを終了します。

  11. コマンドプロンプトを起動して git --version を実行し、git のバージョンが git version 2.15.0.windows.1 になっていることを確認します。

    f:id:ksby:20171124022451p:plain

  12. git-cmd.exe を起動して日本語の表示・入力が問題ないかを確認します。

    f:id:ksby:20171124022622p:plain

  13. 特に問題はないようですので、2.15.0 で作業を進めたいと思います。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その35 )( Geb でテストを作成する2 )

概要

記事一覧はこちらです。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その34 )( Geb でテストを作成する ) の続きです。

  • 今回の手順で確認できるのは以下の内容です。
    • 前回 HtmlUnit でテストを実行してエラーが発生したので、その原因を調査します。
    • Geb で気になる点があるので確認します。

参照したサイト・書籍

目次

  1. HtmlUnit でテストが失敗した原因を取り除いてみる
  2. HtmlUnit は Geb のテスト対象ブラウザから外す
  3. Geb でいろいろ試してみる
    1. 画面の外にボタンがあるとクリックできない?
    2. maxlength を超える文字列は入力できない
    3. FormModule クラスの setValueList メソッドを値セット+Tab キークリックするよう変更する
  4. 次回は。。。

手順

HtmlUnit でテストが失敗した原因を取り除いてみる

前回 HtmlUnit でテストを実行したら java.lang.NoSuchMethodError: com.gargoylesoftware.htmlunit.html.DomElement.getScriptableObject()Ljava/lang/Object; のエラーが出て失敗しました。

ライブラリの依存関係に問題がある気がします。gradlew dependencies コマンドを実行してみたところ、以下の内容が出力されました。

+--- org.seleniumhq.selenium:htmlunit-driver:2.27
|    +--- org.seleniumhq.selenium:selenium-api:3.4.0 -> 3.6.0
|    +--- org.seleniumhq.selenium:selenium-support:3.4.0 -> 3.6.0
|    |    +--- org.seleniumhq.selenium:selenium-api:3.6.0
|    |    +--- org.seleniumhq.selenium:selenium-remote-driver:3.6.0 (*)
|    |    +--- net.bytebuddy:byte-buddy:1.7.5
|    |    +--- org.apache.commons:commons-exec:1.3
|    |    +--- commons-codec:commons-codec:1.10
|    |    +--- commons-logging:commons-logging:1.2
|    |    +--- com.google.code.gson:gson:2.8.0 -> 2.8.1
|    |    +--- com.google.guava:guava:23.0 -> 22.0 (*)
|    |    +--- org.apache.httpcomponents:httpclient:4.5.3 (*)
|    |    +--- org.apache.httpcomponents:httpcore:4.4.6
|    |    +--- net.java.dev.jna:jna:4.1.0 -> 4.2.2
|    |    \--- net.java.dev.jna:jna-platform:4.1.0 (*)
|    \--- net.sourceforge.htmlunit:htmlunit:2.27 -> 2.21
|         +--- xalan:xalan:2.7.2
|         |    \--- xalan:serializer:2.7.2
|         +--- org.apache.commons:commons-lang3:3.4 -> 3.5
|         +--- org.apache.httpcomponents:httpclient:4.5.2 -> 4.5.3 (*)
|         +--- org.apache.httpcomponents:httpmime:4.5.2 -> 4.5.3
|         |    \--- org.apache.httpcomponents:httpclient:4.5.3 (*)
|         +--- commons-codec:commons-codec:1.10
|         +--- net.sourceforge.htmlunit:htmlunit-core-js:2.17
|         +--- net.sourceforge.htmlunit:neko-htmlunit:2.21
|         |    \--- xerces:xercesImpl:2.11.0
|         |         \--- xml-apis:xml-apis:1.4.01
|         +--- net.sourceforge.cssparser:cssparser:0.9.18
|         |    \--- org.w3c.css:sac:1.3
|         +--- commons-io:commons-io:2.4 -> 2.5
|         +--- commons-logging:commons-logging:1.2
|         \--- org.eclipse.jetty.websocket:websocket-client:9.2.15.v20160210 -> 9.4.6.v20170531
|              +--- org.eclipse.jetty:jetty-util:9.4.6.v20170531
|              +--- org.eclipse.jetty:jetty-io:9.4.6.v20170531
|              |    \--- org.eclipse.jetty:jetty-util:9.4.6.v20170531
|              +--- org.eclipse.jetty:jetty-client:9.4.6.v20170531
|              |    +--- org.eclipse.jetty:jetty-http:9.4.6.v20170531
|              |    |    +--- org.eclipse.jetty:jetty-util:9.4.6.v20170531
|              |    |    \--- org.eclipse.jetty:jetty-io:9.4.6.v20170531 (*)
|              |    \--- org.eclipse.jetty:jetty-io:9.4.6.v20170531 (*)
|              \--- org.eclipse.jetty.websocket:websocket-common:9.4.6.v20170531
|                   +--- org.eclipse.jetty.websocket:websocket-api:9.4.6.v20170531
|                   +--- org.eclipse.jetty:jetty-util:9.4.6.v20170531
|                   \--- org.eclipse.jetty:jetty-io:9.4.6.v20170531 (*)
+--- org.seleniumhq.selenium:selenium-support:3.6.0 (*)
+--- org.seleniumhq.selenium:selenium-api:3.6.0
\--- org.seleniumhq.selenium:selenium-remote-driver:3.6.0 (*)

net.sourceforge.htmlunit:htmlunit:2.27 -> 2.21 とバージョンダウンされているモジュールがありました。net.sourceforge.htmlunit:htmlunit の 2.27 が使用されるよう build.gradle を以下のように変更します。

dependencies {
    ..........

    // for Geb + Spock
    ..........
    testCompile("org.seleniumhq.selenium:htmlunit-driver:2.27")
    testCompile("net.sourceforge.htmlunit:htmlunit:2.27")
    ..........
}
  • testCompile("net.sourceforge.htmlunit:htmlunit:2.27") を追加します。

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

再び gradlew dependencies コマンドを実行すると、今度は net.sourceforge.htmlunit:htmlunit:2.27 と出力されました。

+--- org.seleniumhq.selenium:htmlunit-driver:2.27
|    +--- org.seleniumhq.selenium:selenium-api:3.4.0 -> 3.6.0
|    +--- org.seleniumhq.selenium:selenium-support:3.4.0 -> 3.6.0
|    |    +--- org.seleniumhq.selenium:selenium-api:3.6.0
|    |    +--- org.seleniumhq.selenium:selenium-remote-driver:3.6.0 (*)
|    |    +--- net.bytebuddy:byte-buddy:1.7.5
|    |    +--- org.apache.commons:commons-exec:1.3
|    |    +--- commons-codec:commons-codec:1.10
|    |    +--- commons-logging:commons-logging:1.2
|    |    +--- com.google.code.gson:gson:2.8.0 -> 2.8.1
|    |    +--- com.google.guava:guava:23.0 -> 22.0 (*)
|    |    +--- org.apache.httpcomponents:httpclient:4.5.3 (*)
|    |    +--- org.apache.httpcomponents:httpcore:4.4.6
|    |    +--- net.java.dev.jna:jna:4.1.0 -> 4.2.2
|    |    \--- net.java.dev.jna:jna-platform:4.1.0 (*)
|    \--- net.sourceforge.htmlunit:htmlunit:2.27
|         +--- xalan:xalan:2.7.2
|         |    \--- xalan:serializer:2.7.2
|         +--- org.apache.commons:commons-lang3:3.5
|         +--- org.apache.httpcomponents:httpmime:4.5.3
|         |    \--- org.apache.httpcomponents:httpclient:4.5.3 (*)
|         +--- net.sourceforge.htmlunit:htmlunit-core-js:2.27
|         +--- net.sourceforge.htmlunit:neko-htmlunit:2.27
|         |    \--- xerces:xercesImpl:2.11.0
|         |         \--- xml-apis:xml-apis:1.4.01
|         +--- net.sourceforge.cssparser:cssparser:0.9.23
|         |    \--- org.w3c.css:sac:1.3
|         +--- commons-io:commons-io:2.5
|         +--- commons-logging:commons-logging:1.2
|         \--- org.eclipse.jetty.websocket:websocket-client:9.4.5.v20170502 -> 9.4.6.v20170531
|              +--- org.eclipse.jetty:jetty-util:9.4.6.v20170531
|              +--- org.eclipse.jetty:jetty-io:9.4.6.v20170531
|              |    \--- org.eclipse.jetty:jetty-util:9.4.6.v20170531
|              +--- org.eclipse.jetty:jetty-client:9.4.6.v20170531
|              |    +--- org.eclipse.jetty:jetty-http:9.4.6.v20170531
|              |    |    +--- org.eclipse.jetty:jetty-util:9.4.6.v20170531
|              |    |    \--- org.eclipse.jetty:jetty-io:9.4.6.v20170531 (*)
|              |    \--- org.eclipse.jetty:jetty-io:9.4.6.v20170531 (*)
|              \--- org.eclipse.jetty.websocket:websocket-common:9.4.6.v20170531
|                   +--- org.eclipse.jetty.websocket:websocket-api:9.4.6.v20170531
|                   +--- org.eclipse.jetty:jetty-util:9.4.6.v20170531
|                   \--- org.eclipse.jetty:jetty-io:9.4.6.v20170531 (*)
+--- net.sourceforge.htmlunit:htmlunit:2.27 (*)
+--- org.seleniumhq.selenium:selenium-support:3.6.0 (*)
+--- org.seleniumhq.selenium:selenium-api:3.6.0
\--- org.seleniumhq.selenium:selenium-remote-driver:3.6.0 (*)

再度 HtmlUnit でテストを実行してみると、今度は成功するテストが出てきました。ただし、まだ一部のテストが失敗しています。失敗したテストを見てみると、

f:id:ksby:20171121005024p:plain

こちらは autocomplete のテストで失敗しており、5秒経過しても指定した selector が見つからないようです。

f:id:ksby:20171121005223p:plain

こちらはエラーメッセージが表示されていないようです。

いろいろ試してみると、後者のエラーメッセージが出ない方はテストを以下のように変更すると解消されました。

        when: "電話番号と郵便番号を入力する"
        $("#inquiryInput02Form").tel1 = tel1 << Keys.TAB
        $("#inquiryInput02Form").tel2 = tel2 << Keys.TAB
        $("#inquiryInput02Form").tel3 = tel3 << Keys.TAB
        $("#inquiryInput02Form").email = email << Keys.TAB

↓↓↓

        when: "電話番号と郵便番号を入力する"
        $("#inquiryInput02Form").tel1 = tel1
        $("#tel2") << ""
        $("#inquiryInput02Form").tel2 = tel2
        $("#tel3") << ""
        $("#inquiryInput02Form").tel3 = tel3
        $("#email") << ""
        $("#inquiryInput02Form").email = email
        $("#tel1") << ""

テストを実行すると autocomplete 以外のテストが成功します。

f:id:ksby:20171122011927p:plain

HtmlUnit だと Keys.TAB を送信しても次の入力項目にフォーカスが移動せず、フォーカスを移動したい入力項目に << "" を送信しないといけないようです。

ということは autocomplete のテストを以下のように修正すると、

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

↓↓↓

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

今度は全てのテストが成功しました。

f:id:ksby:20171122012953p:plain

HtmlUnitGeb のテスト対象ブラウザから外す

$("#inquiryInput02Form").tel1 = tel1 << Keys.TAB のように記述して TAB キーを押したら次の入力項目にフォーカスが移動できた方が個人的には分かりやすく、かつテストが書きやすいのと、FirefoxChrome の headless が十分使いやすいので、HtmlUnitGeb のテスト対象ブラウザから外すことにします。

build.gradle を以下のように変更します。

dependencies {
    ..........

    // 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}")
}

..........

// for Geb + Spock Integration Test
def drivers = ["chrome", "firefox"]
drivers.each { driver ->
    task "${driver}Test"(type: Test) {
        // 前回実行時以降に何も更新されていなくても必ず実行する
        outputs.upToDateWhen { false }
        systemProperty "geb.env", driver
        exclude "ksbysample/**"
    }
}
task gebTest {
    dependsOn drivers.collect { tasks["${it}Test"] }
    enabled = false
}
  • 以下の2行を削除します。
    • testCompile("org.seleniumhq.selenium:htmlunit-driver:2.27")
    • testCompile("net.sourceforge.htmlunit:htmlunit:2.27")
  • def drivers = ["chrome", "firefox", "htmlunit"]def drivers = ["chrome", "firefox"] に変更します。

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

src/test/resources/GebConfig.groovy を以下のように変更します。

environments {

    chrome {
        driver = {
            ChromeOptions chromeOptions = new ChromeOptions()
            chromeOptions.setHeadless(true)
            new ChromeDriver(chromeOptions)
        }
    }

    firefox {
        driver = {
            FirefoxOptions firefoxOptions = new FirefoxOptions()
            firefoxOptions.setHeadless(true)
            new FirefoxDriver(firefoxOptions)
        }
    }

}
  • environments から htmlunit { new HtmlUnitDriver(true) } を削除します。

gebTest タスクを実行して "BUILD SUCCESSFUL" が表示されることを確認します。

f:id:ksby:20171123230003p:plain

Geb でいろいろ試してみる

画面の外にボタンがあるとクリックできない?

Geb について書かれた記事を見ていると、ボタンがブラウザの Window 内に表示されていない場合クリックできないと書かれたものを何回か見かけので、確認してみます。

まずは <br> タグを大量に入れて、「次へ」ボタンを画面の外に追い出します。

f:id:ksby:20171123230939p:plain

src/test/groovy/geb/gebspec/inquiry/InquiryTestSpec.groovy に以下のテストを記述します。

    def "次へボタンが画面の外にあるとクリックできない?" () {
        setup: "入力画面1を表示してデータを入力する"
        to InquiryInput01Page
        form.setValueList(maxLengthValueList)
        $("#inquiryInput01Form").sex = "1"
        // 念のため、フォーカスを「お名前(漢字)」へ移動する
        $("#lastname") << ""

        expect: "次へボタンをクリックする"
        form.btnNext.click(InquiryInput02Page)
    }

テストを実行すると問題なく成功しますね。。。

f:id:ksby:20171123232224p:plain

src/test/resources/GebConfig.groovy を以下のように変更して headless モードを解除し、

driver = {
    FirefoxOptions firefoxOptions = new FirefoxOptions()
//    firefoxOptions.setHeadless(true)
    new FirefoxDriver(firefoxOptions)
}

src/test/groovy/geb/gebspec/inquiry/InquiryTestSpec.groovy に Thread.sleep(3000) を追加して、

    def "次へボタンが画面の外にあるとクリックできない?" () {
        setup: "入力画面1を表示してデータを入力する"
        to InquiryInput01Page
        form.setValueList(maxLengthValueList)
        $("#inquiryInput01Form").sex = "1"
        // 念のため、フォーカスを「お名前(漢字)」へ移動する
        $("#lastname") << ""
        Thread.sleep(3000)

        expect: "次へボタンをクリックする"
        form.btnNext.click(InquiryInput02Page)
    }

テストを実行してブラウザの動きを見てみたところ、form.btnNext.click(InquiryInput02Page) の処理を実行する時に「次へ」ボタンへフォーカスが移動して画面の見える位置に移動していました。Firefox Quantum だと問題なく動作するようです。

src/test/resources/GebConfig.groovy を以下のように変更してブラウザを Chrome に変更しても、

driver = {
//    FirefoxOptions firefoxOptions = new FirefoxOptions()
//    firefoxOptions.setHeadless(true)
//    new FirefoxDriver(firefoxOptions)

    ChromeOptions chromeOptions = new ChromeOptions()
    chromeOptions.setHeadless(true)
    new ChromeDriver(chromeOptions)
}

テストは成功しました。Chrome も問題ないようです。

f:id:ksby:20171123232814p:plain

gradle から gebTest タスクを実行しても成功しました。

f:id:ksby:20171123233129p:plain

画面の外にボタンがあっても問題なくクリックできるようです。変更したソースは元に戻します。

maxlength を超える文字列は入力できない

Geb で値をセットした時に maxlength を超える文字列がセットされるのか気になったので、確認してみます。

src/test/groovy/geb/gebspec/inquiry/InquiryTestSpec.groovy に以下のテストを記述して、

    def "maxlength を超えた文字列は入力できない?"() {
        setup:
        to InquiryInput01Page
        // lastname の maxlength は 20 なので 21文字セットしてみる
        $("#lastname").value("あ" * 20 + "い")

        expect:
        $("#lastname") == "あ" * 20 + "い"
    }

テストを実行すると失敗しました。21文字目の "い" がセットされていませんでした。

f:id:ksby:20171123235347p:plain

maxlength を超える文字列はセットできないようです。

FormModule クラスの setValueList メソッドを値セット+Tab キークリックするよう変更する

src/test/groovy/geb/module/FormModule.groovy の setValueList メソッドは今は以下の実装ですが、

    void setValueList(valueList) {
        valueList.each {
            $(it.key).value(it.value)
        }
    }

空のデータをセットしてエラーメッセージを表示する以下のテストを実行した時に、

    def "テスト"() {
        setup:
        to InquiryInput01Page
        form.setValueList(initialValueList)
        Thread.sleep(10000)

        expect:
        true
    }

年齢に空文字列をセットしたのに、フォーカスが移動していないのでエラーメッセージが表示されていないことに気づきました。

f:id:ksby:20171124001756p:plain

src/test/groovy/geb/module/FormModule.groovy を以下のよう変更し、値をセットした後にTABキーをクリックして blur イベントが発生するようにします。

    void setValueList(valueList) {
        valueList.each {
            $(it.key).value(it.value)
            $(it.key) << Keys.TAB
        }
    }
  • $(it.key) << Keys.TAB を追加します。

これでさっきのテストを実行すると、今度は年齢に空文字列をセットしてTABキーがクリックされるのでエラーメッセージが表示されます。

f:id:ksby:20171124002255p:plain

次回は。。。

Geb を使用したテストの作り方が簡単ながらも分かったので一旦ここで終了します。入力画面3に進む前に少し迷走する予定です。。。

履歴

2017/11/24
初版発行。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その34 )( Geb でテストを作成する )

概要

記事一覧はこちらです。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その33 )( ESLint を導入する ) の続きです。

  • 今回の手順で確認できるのは以下の内容です。
    • Geb で入力画面1、入力画面2のテストを作成してみます。
    • Form に値を一括セット/検証する方法を調べていたら結構時間がかかってしまいました。

参照したサイト・書籍

  1. The Book Of Geb
    http://www.gebish.org/manual/current/

  2. Gebチートシート
    https://qiita.com/itagakishintaro/items/1fa06904bd0a6de73ee2

  3. Geb Advent Calendar 2016
    https://qiita.com/advent-calendar/2016/geb

  4. Geb 自分用メモ
    http://bufferings.hatenablog.com/entry/2015/06/11/010321

  5. Web画面自動テストフレームワークGeb」の紹介
    http://lab.astamuse.co.jp/entry/geb_test_01

  6. Gebチュートリアル
    https://www.atware.co.jp/blog/2015/9/12/geb

  7. Can selenium handle autocomplete?
    https://stackoverflow.com/questions/663034/can-selenium-handle-autocomplete

目次

  1. Firefox Quantum がインストールされていました
  2. FormModule クラスを作成する
  3. 入力画面1の InquiryInput01Page クラスを変更し、テストデータを用意する
  4. 入力画面2の InquiryInput02Page クラスを作成し、テストデータを用意する
  5. Geb のテストを作成する
  6. 続きます。。。

手順

Firefox Quantum がインストールされていました

Firefox を再起動したら Firefox Quantum になっていました。そういえば正式リリース日でしたね(2017/11/15 に書いています)。既存の Firefox とは別にインストールする必要があるのかな、と思っていたら自動でアップデートされていました。

f:id:ksby:20171115012802p:plain

サンプルで書いていた Geb のテストを試したら問題なく動くようなので、このまま続けます。

FormModule クラスを作成する

画面共通のボタンを定義したり、Form に値を一括セット/検証するメソッドを作成したいので、Geb の Module を継承したクラスを作成します。

src/test/groovy/geb の下に module パッケージを作成します。src/test/groovy/geb/module の下に FormModule.groovy を新規作成し、以下の内容を記述します。

package geb.module

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

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

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

    /**
     * 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 {
            $(it.key).value(it.value)
        }
    }

    /**
     * セレクタに値がセットされているかを検証する
     * このメソッドは Spock の then, expect で使用する想定である
     *
     * @param valueList 検証するセレクタと値を記述した Map
     * @return true 固定
     */
    boolean assertValueList(valueList) {
        valueList.each { key, value ->
            if ($(key).first().attr("type") == "radio") {
                WebElement element = $(key).allElements().find { it.selected }
                if (element == null) {
                    assert null == value
                } else {
                    assert element.getAttribute("value") == value
                }
            } else {
                assert $(key).value() == value
            }
        }
        true
    }

}

入力画面1の InquiryInput01Page クラスを変更し、テストデータを用意する

入力画面1用に作成していた InquiryInput01Page クラスで FormModule を使用するようにし、かつテストで使用するデータも記述します。src/test/groovy/geb/page/inquiry/InquiryInput01Page.groovy を以下のように変更します。

package geb.page.inquiry

import geb.Page
import geb.module.FormModule

class InquiryInput01Page extends Page {

    static url = "/inquiry/input/01"
    static at = { title == "入力フォーム - 入力画面1" }
    static content = {
        form { module FormModule }
    }

    static initialValueList = [
            "#lastname"        : "",
            "#firstname"       : "",
            "#lastkana"        : "",
            "#firstkana"       : "",
            "input[name='sex']": null,
            "#age"             : "",
            "#job"             : ""
    ]

    static maxLengthValueList = [
            "#lastname" : "あ" * 20,
            "#firstname": "あ" * 20,
            "#lastkana" : "あ" * 20,
            "#firstkana": "あ" * 20,
            "#age"      : "9" * 3,
    ]

}
  • static content = { ... } から btnNext { $(".js-btn-next") } を削除し、form { module FormModule } を追加します。
  • static initialValueList = [ ... ] を追加します。
  • static maxLengthValueList = [ ... ] を追加します。

入力画面2の InquiryInput02Page クラスを作成し、テストデータを用意する

入力画面2用の InquiryInput02Page クラスを作成します。テストで使用するデータも記述します。

src/test/groovy/geb/page/inquiry の下に InquiryInput02Page.groovy を新規作成し、以下の内容を記述します。

package geb.page.inquiry

import geb.Page
import geb.module.FormModule

class InquiryInput02Page extends Page {

    static url = "/inquiry/input/01"
    static at = { title == "入力フォーム - 入力画面2" }
    static content = {
        form { module FormModule }
    }

    static initialValueList = [
            "#zipcode1": "",
            "#zipcode2": "",
            "#address" : "",
            "#tel1"    : "",
            "#tel2"    : "",
            "#tel3"    : "",
            "#email"   : "",
    ]

    static maxLengthValueList = [
            "#zipcode1": "x" * 3,
            "#zipcode2": "x" * 4,
            "#address" : "あ" * 256,
            "#tel1"    : "9" * 5,
            "#tel2"    : "9" * 4,
            "#tel3"    : "9" * 4,
            "#email"   : "x" * 256,
    ]

}

Geb のテストを作成する

入力画面1、入力画面2でできるテストを作成してみます。

src/test/groovy/geb/gebspec の下に inquiry パッケージを作成した後、inquiry パッケージの下に InquiryTestSpec.groovy を新規作成して以下の内容を記述します。

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)
        $("#inquiryInput01Form").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)
        $("#inquiryInput01Form").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)
        $("#inquiryInput01Form").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)
        $("#inquiryInput01Form").sex = "1"
        form.btnNext.click(InquiryInput02Page)

        when: "電話番号と郵便番号を入力する"
        $("#inquiryInput02Form").tel1 = tel1 << Keys.TAB
        $("#inquiryInput02Form").tel2 = tel2 << Keys.TAB
        $("#inquiryInput02Form").tel3 = tel3 << Keys.TAB
        $("#inquiryInput02Form").email = email << Keys.TAB

        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桁の数字を入力してください"              | ""
    }

}

テストを書いてみて思ったのは、以下の点でした。

  • InquiryInput01Page にも InquiryInput02Page にも static content = { form { module FormModule } } と書いており、テストは form.~ から書いて問題なく動作するのかな?と不思議に思っていましたが、to ...form.btnNext.click(...Page) を呼び出して画面遷移した後の Page クラスの form を見てくれるようです。
  • 値をセットするだけなら不要ですが、Javascriptblur イベントを発生させたい場合には << Keys.TAB が必須でした。
  • autocomplete のテストは時間がかかりましたが、それ以外は MockMvc と比較するとテストが書きやすいです。ただし実行に時間がかかるので、何でも Geb でテストを作成するという訳にはいかなそうです。

作成したテストを実行してみます。Tomcat を起動した後、テストを実行します。

f:id:ksby:20171117002300p:plain

テストは全て成功しました。最初のテストが 10数秒、それ以降のテストが2~4秒程度かかるようです。ブラウザを起動してテストをするので、やっぱり結構時間がかかりますね。

Headless Chrome に切り替えてみます。src/test/resources/GebConfig.groovy を以下のように変更します。

driver = {
//    FirefoxOptions firefoxOptions = new FirefoxOptions()
//    firefoxOptions.setHeadless(true)
//    new FirefoxDriver(firefoxOptions)
    ChromeOptions chromeOptions = new ChromeOptions()
    chromeOptions.setHeadless(true)
    new ChromeDriver(chromeOptions)
}

テストを実行します。

f:id:ksby:20171117003317p:plain

Firefox headless モードの時と同様にテストは全て成功しました。Firefox headless モードと比較すると最初のテストは数秒速いですが、その後のテストが 0.5~2秒くらい遅い気がします。

最後に HtmlUnit に切り替えてみます。src/test/resources/GebConfig.groovy を以下のように変更します。

driver = {
//    FirefoxOptions firefoxOptions = new FirefoxOptions()
//    firefoxOptions.setHeadless(true)
//    new FirefoxDriver(firefoxOptions)
    new HtmlUnitDriver(true)
}

テストを実行します。

f:id:ksby:20171117004939p:plain

なぜか全部失敗しましたね。。。 java.lang.NoSuchMethodError: com.gargoylesoftware.htmlunit.html.DomElement.getScriptableObject()Ljava/lang/Object; というエラーが出ています。

続きます。。。

HtmlUnit で失敗する原因を調べます。また Geb でもう少しいろいろ試してみます。

履歴

2017/11/17
初版発行。