かんがるーさんの日記

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

Spring Boot + npm + Geb で入力フォームを作ってテストする ( 番外編 )( IntelliJ IDEA に Rainbow Brackets Plugin をインストールする )

概要

記事一覧はこちらです。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その73 )( Spring Boot を 1.5.14 → 2.0.4 へバージョンアップする ) の続きです。

Twitter を見ていたら、Rainbow Brackets という IntelliJ IDEA の Plugin の情報が流れてきました。括弧の対応に色がついて見やすくなるようです。確かに便利そうなのでインストールしてみます。

参照したサイト・書籍

目次

  1. Rainbow Brackets Plugin をインストールする
  2. Rainbow Brackets Plugin を使ってみる

手順

Rainbow Brackets Plugin をインストールする

IntelliJ IDEA のメインメニューから「File」-「Settings…」を選択します。

「Settings」ダイアログが表示されます。画面左側のリストから「Plugins」を選択した後、画面中央下にある「Browse repositories…」ボタンをクリックします。

f:id:ksby:20180804071634p:plain

「Browse Repositories」ダイアログが表示されます。左上の検索フィールドに “rainbow” と入力すると、その下のリストに Rainbow Brackets Plugin が表示されますので、画面右側に表示される「Install」ボタンをクリックします。

f:id:ksby:20180804072010p:plain

Plugin がダウンロードされた後「Install」ボタンが「Restart IntelliJ IDEA」ボタンに変わりますので、クリックします。

一旦「Settings」ダイアログに戻りますので、画面下の「OK」ボタンをクリックします。

IDE and Plugin Updates」ダイアログが表示されますので「Restart」ボタンをクリックします。

IntelliJ IDEA が再起動してインストールは完了です。

Rainbow Brackets Plugin を使ってみる

ソースを開いてみると、Rainbow Brackets Plugin インストール前はこんな感じでしたが、

f:id:ksby:20180804073456p:plain

括弧毎に色が変わって表示されていました。確かにこれは見やすいです。

f:id:ksby:20180804073107p:plain

また Ctrl + 右クリックすると括弧の範囲内を色を付けて表示してくれます。確かにこれは便利です。。。

f:id:ksby:20180804074555p:plain f:id:ksby:20180804074646p:plain f:id:ksby:20180804074740p:plain f:id:ksby:20180804074851p:plain

Alt + 右クリックだと括弧の範囲内以外をグレー表示にします。

f:id:ksby:20180804075323p:plain f:id:ksby:20180804075503p:plain f:id:ksby:20180804075602p:plain f:id:ksby:20180804075710p:plain

Java のソース以外にも HTML でもこの機能が有効です。

HTML の場合にはタグで囲まれた範囲内を色を付けて表示してくれます(ただしたまにうまく動作しない時があって、その時は IntelliJ IDEA を再起動すると直ります)。

f:id:ksby:20180804081204p:plain f:id:ksby:20180804081256p:plain f:id:ksby:20180804081451p:plain f:id:ksby:20180804081633p:plain

上の機能ですが、Rainbow Brackets のページでは Ctrl + Button3 と書かれているのですが、Button3 が最初何だか分からなかったんですよね。。。 IntelliJ Platform SDK DevGuide - Action System のページの中の "button1", "button2", "button3" for the mouse buttons; という記述を見つけて、マウスのボタンのことだと分かりました。

履歴

2018/08/04
初版発行。

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

概要

記事一覧はこちらです。

感想 の続きです。

  • 今回の手順で確認できるのは以下の内容です。
    • Spring Boot を 1系(1.5.14) → 2系(2.0.4) へバージョンアップします。

参照したサイト・書籍

目次

  1. build.gradle を変更する
  2. エラーの出ているクラスを変更する
    1. src/main/java/ksbysample/webapp/bootnpmgeb/config/ApplicationConfig.java
    2. src/main/java/ksbysample/webapp/bootnpmgeb/config/WebMvcConfig.java
    3. src/main/java/ksbysample/webapp/bootnpmgeb/config/WebSecurityConfig.java
  3. application.properties で spring.session.store-type の設定を hash_mapjdbc に変更する(hash_map はなくなりました)
  4. spring-session-jdbc を追加する
  5. Build Project(Ctrl+F9)を実行して出るエラーを取り除く
    1. org.hibernate.validator.constraints.NotEmptyjavax.validation.constraints.NotEmpty に変更する
  6. clean タスク → Rebuild Project → build タスクを実行する
  7. テストが失敗した原因を調査する
    1. java.lang.NoSuchMethodError: org.mockito.internal.handler.MockHandlerFactory.createMockHandler(Lorg/mockito/mock/MockCreationSettings;)Lorg/mockito/internal/InternalMockHandler;
  8. Tomcat を起動して、起動時に spring-boot-properties-migrator が出力するメッセージに対応する
    1. Key: security.basic.enabled、Reason: The security auto-configuration is no longer customizable.
  9. gebTest を実行してみる
  10. ブラウザで画面を表示して入力してみる
  11. 作成された jar ファイルの中身を確認する
  12. 本番稼働用ディレクトリに jar ファイルをコピーして動作確認する
  13. build.gradle から runtimeOnly("org.springframework.boot:spring-boot-properties-migrator") を削除する
  14. メモ書き

手順

build.gradle を変更する

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

buildscript {
    ext {
        group "ksbysample"
        version "1.0.2-RELEASE"
    }
}

plugins {
    id "java"
    id "eclipse"
    id "idea"
    id "org.springframework.boot" version "2.0.4.RELEASE"
    id "io.spring.dependency-management" version "1.0.6.RELEASE"
    id "groovy"
    id "net.ltgt.errorprone" version "0.0.14"
    id "checkstyle"
    id "findbugs"
    id "pmd"
    id "com.moowork.node" version "1.2.0"
}

sourceCompatibility = 1.8
targetCompatibility = 1.8

wrapper {
    gradleVersion = '4.8.1'
    distributionType = Wrapper.DistributionType.ALL
}

[compileJava, compileTestGroovy, compileTestJava]*.options*.compilerArgs = ['-Xlint:all,-options,-processing,-path']
compileJava.options.compilerArgs += [
        '-Xep:RemoveUnusedImports:WARN'
        , '-Xep:ParameterName: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.11'
    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) {
    reports {
        xml.enabled = false
        html.enabled = true
    }
}

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

repositories {
    mavenCentral()
}

dependencyManagement {
    imports {
        // mavenBom は以下の URL のものを使用する
        // https://repo.spring.io/release/org/springframework/boot/spring-boot-starter-parent/2.0.4.RELEASE/
        // bomProperty に指定可能な property は以下の URL の BOM に記述がある
        // https://repo.spring.io/release/org/springframework/boot/spring-boot-dependencies/2.0.4.RELEASE/spring-boot-dependencies-2.0.4.RELEASE.pom
        mavenBom org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES
    }
}

dependencies {
    def spockVersion = "1.1-groovy-2.4"
    def domaVersion = "2.19.2"
    def lombokVersion = "1.18.0"
    def errorproneVersion = "2.3.1"
    def powermockVersion = "1.7.4"
    def seleniumVersion = "3.13.0"

    // dependency-management-plugin によりバージョン番号が自動で設定されるもの
    // Appendix F. Dependency versions ( https://docs.spring.io/spring-boot/docs/2.0.4.RELEASE/reference/html/appendix-dependency-versions.html ) 参照
    implementation("org.springframework.boot:spring-boot-starter-web")
    implementation("org.springframework.boot:spring-boot-starter-thymeleaf") {
        exclude group: "org.codehaus.groovy", module: "groovy"
    }
    implementation("org.springframework.boot:spring-boot-starter-data-jpa")
    implementation("org.springframework.boot:spring-boot-starter-freemarker")
    implementation("org.springframework.boot:spring-boot-starter-mail")
    implementation("org.springframework.boot:spring-boot-starter-security")
    runtimeOnly("org.springframework.boot:spring-boot-devtools")
    implementation("org.springframework.session:spring-session-core")
    implementation("org.springframework.session:spring-session-jdbc")
    implementation("org.codehaus.janino:janino")
    implementation("org.apache.tomcat:tomcat-jdbc")
    testImplementation("org.springframework.boot:spring-boot-starter-test")
    testImplementation("org.springframework.security:spring-security-test")
    testImplementation("org.yaml:snakeyaml")
    testImplementation("org.mockito:mockito-core")

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

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

    // for Doma
    annotationProcessor("org.seasar.doma:doma:${domaVersion}")
    implementation("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
    testImplementation("org.powermock:powermock-module-junit4:${powermockVersion}")
    testImplementation("org.powermock:powermock-api-mockito2:${powermockVersion}")

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

    // for Spring Boot 2.0 Migration
    runtimeOnly("org.springframework.boot:spring-boot-properties-migrator")
}

..........
  • buildscript block の以下の点を変更します。
    • Web アプリのバージョン番号を version "1.0.1-RELEASE"version "1.0.2-RELEASE" に変更します。
    • Spring Boot のバージョン番号は plugins block の id "org.springframework.boot" のところで指定するようになるので、springBootVersion = "1.5.14.RELEASE" を削除します。
  • plugins block の以下の点を変更します。
    • // plugins {} block 内では ${springBootVersion} が使用できないので、バージョンを直接記述している のコメントを削除します。
    • id "org.springframework.boot" version "1.5.14.RELEASE"id "org.springframework.boot" version "2.0.4.RELEASE" に変更します。
    • id "io.spring.dependency-management" version "1.0.6.RELEASE" を追加します。
  • 明記しなくても問題ないので bootRepackage { ... } を削除します。
  • dependencyManagement block の以下の点を変更します。
    • mavenBom("org.springframework.boot:spring-boot-starter-parent:${springBootVersion}")mavenBom org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES に変更します。
    • Appendix F. Dependency versions を見ると Thymeleaf のバージョンが 3.0.9.RELEASE になっているので、以下の5行を削除します。
      • 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'
  • dependencies block の以下の点を変更します。
    • compile(...)implementation(...) に変更します。
    • Appendix F. Dependency versions を見ると spring-session は存在せず、spring-session-corespring-session-data-gemfire のように後ろに -... が付くようになっていました。implementation("org.springframework.session:spring-session")implementation("org.springframework.session:spring-session-core") に変更します。
    • Spring Boot 2 では HikariCP がデフォルトのコネクションプーリングに変わり、Tomcat JDBC Connection Pool はデフォルトでは入りません。今は HikariCP には変更しないので implementation("org.apache.tomcat:tomcat-jdbc") を追加して入れるようにします。
    • testImplementation("org.powermock:powermock-api-mockito:${powermockVersion}")testImplementation("org.powermock:powermock-api-mockito2:${powermockVersion}") に変更します。
    • Spring Boot 2.0 Migration Guide - Before You Start の記述に従い runtimeOnly("org.springframework.boot:spring-boot-properties-migrator") を追加します。

変更後、Gradle Tool Window の左上にある「Refresh all Gradle projects」ボタンをクリックして更新すると、特にエラーは出ずに更新されました。

エラーの出ているクラスを変更する

src/main/java/ksbysample/webapp/bootnpmgeb/config/ApplicationConfig.java

f:id:ksby:20180731011156p:plain f:id:ksby:20180731012343p:plain

  • DataSourceBuilder クラスが import できずエラーが出ていました。import 文を削除した後、Alt+Enter を押して import し直します。

src/main/java/ksbysample/webapp/bootnpmgeb/config/WebMvcConfig.java

f:id:ksby:20180731012914p:plain

  • WebMvcConfigurerAdapter が非推奨になりました。WebMvcConfigurerAdapter クラスの JavaDoc の説明を読むと WebMvcConfigurer インターフェースに Java 8 のデフォルトメソッドで実装が追加されたので WebMvcConfigurer インターフェースを使用するよう記述されていました。extends WebMvcConfigurerAdapterimplements WebMvcConfigurer に変更します。

    f:id:ksby:20180731013237p:plain

  • SpringTemplateEngine クラスが import できずエラーが出ていました。import 文を削除した後、Alt+Enter を押して import class し直します。

src/main/java/ksbysample/webapp/bootnpmgeb/config/WebSecurityConfig.java

f:id:ksby:20180731013952p:plain

  • 28. Security から @Order(SecurityProperties.ACCESS_OVERRIDE_ORDER) の記述が削除されていたので、おそらく無くなったものと思われます。@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER) を削除します。

application.properties で spring.session.store-type の設定を hash_mapjdbc に変更する(hash_map はなくなりました)

f:id:ksby:20180731015744p:plain

spring.session.store-typehash_map が指定できなくなっていました。実際選択可能な候補を表示させてみると hash_map がありません。

f:id:ksby:20180731020018p:plain

理由が書かれているのは Better detection of Spring Session store のページのようです。今回は hash_mapjdbc に変更して、spring-session-jdbc を入れることにします。

spring-session-jdbc を追加する

build.gradle に implementation("org.springframework.session:spring-session-jdbc") を追加します。

dependencies {
    ..........
    runtimeOnly("org.springframework.boot:spring-boot-devtools")
    implementation("org.springframework.session:spring-session-core")
    implementation("org.springframework.session:spring-session-jdbc")
    implementation("org.codehaus.janino:janino")
    ..........

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

src/main/resources/application.properties に以下の設定を追加します。

spring.session.store-type=jdbc
spring.session.jdbc.initialize-schema=embedded
spring.session.jdbc.schema=classpath:org/springframework/session/jdbc/schema-h2.sql
spring.session.jdbc.table-name=SPRING_SESSION
  • 以下の3行を追加します。
    • spring.session.jdbc.initialize-schema=embedded
    • spring.session.jdbc.schema=classpath:org/springframework/session/jdbc/schema-h2.sql
    • spring.session.jdbc.table-name=SPRING_SESSION
  • spring.session.jdbc.schema に設定する SQL ファイルは org.springframework.session:spring-session-jdbc のライブラリの下を見ると分かります。

    f:id:ksby:20180731052251p:plain

Build Project(Ctrl+F9)を実行して出るエラーを取り除く

org.hibernate.validator.constraints.NotEmptyjavax.validation.constraints.NotEmpty に変更する

Ctrl+F9 を押して Build Project を実行すると org.hibernate.validator.constraintsのorg.hibernate.validator.constraints.NotEmptyは非推奨になりました のメッセージが表示されました。

f:id:ksby:20180731233816p:plain

org.hibernate.validator.constraints.NotEmpty クラスのソースを見ると以下の画像の記述がありますので、javax.validation.constraints.NotEmpty に変更します。

f:id:ksby:20180731234056p:plain

変更したのは以下のソースです。import 文を削除した後、Alt+Enter で import し直しました。

  • src/main/java/ksbysample/webapp/bootnpmgeb/web/inquiry/form/InquiryInput01Form.java
  • src/main/java/ksbysample/webapp/bootnpmgeb/web/inquiry/form/InquiryInput02FormNotEmptyRule.java
  • src/main/java/ksbysample/webapp/bootnpmgeb/web/inquiry/form/InquiryInput03FormNotEmptyRule.java

clean タスク → Rebuild Project → build タスクを実行する

clean タスク → Rebuild Project → build タスクを実行してみると、以下の画像のような結果でした。

f:id:ksby:20180801214047p:plain f:id:ksby:20180801214147p:plain

  • FindBugsFindBugs rule violations were found. のメッセージが表示されました。
  • 147 tests completed, 1 failed とテストが1件失敗しました。

FindBugs の方は一旦後回しにして、まずはテストが失敗した原因を取り除きます。

テストが失敗した原因を調査する

java.lang.NoSuchMethodError: org.mockito.internal.handler.MockHandlerFactory.createMockHandler(Lorg/mockito/mock/MockCreationSettings;)Lorg/mockito/internal/InternalMockHandler;

build タスクの出力内容では原因が分からないので、Project Tool Window の src/test/groovy/ksbysample から「Run 'Tests in 'ksbysample''」を実行します。

f:id:ksby:20180801215014p:plain

失敗したテストは以下の画像のテストでした。java.lang.NoSuchMethodError: org.mockito.internal.handler.MockHandlerFactory.createMockHandler(Lorg/mockito/mock/MockCreationSettings;)Lorg/mockito/internal/InternalMockHandler; というエラーメッセージが出ています。

f:id:ksby:20180801215301p:plain

エラーメッセージで検索すると There is a issue, after I downgrade the Mockito version, it disappeared という Issue が見つかりました。PowerMock のバージョンを 2.0.0-beta.5 にすると直るようです。

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

dependencies {
    def spockVersion = "1.1-groovy-2.4"
    def domaVersion = "2.19.2"
    def lombokVersion = "1.18.0"
    def errorproneVersion = "2.3.1"
    def powermockVersion = "2.0.0-beta.5"
    def seleniumVersion = "3.13.0"
  • def powermockVersion = "1.7.4"def powermockVersion = "2.0.0-beta.5" に変更します。

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

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

f:id:ksby:20180801220702p:plain

Tomcat を起動して、起動時に spring-boot-properties-migrator が出力するメッセージに対応する

Tomcat を起動すると以下のメッセージが表示されました。

f:id:ksby:20180801224201p:plain

また、少し放置していたら、1分毎に無効になったセッションを削除するための DELETE 文が実行されていました。なるほど、spring-session-jdbc だとこういう動作になるんですね。

f:id:ksby:20180801224436p:plain

Key: security.basic.enabled、Reason: The security auto-configuration is no longer customizable.

Spring Boot 2.0 Migration Guide - SecuritySpring Boot Security 2.0 を読んだ限りでは、application.properties から security.basic.enabled は設定できなくなり、設定するなら WebSecurityConfigurerAdapter の継承クラスで設定するとのこと。

試しに src/main/resources/application-develop.properties から security.basic.enabled=false の設定を削除した後 Tomcat を起動すると、Key: security.basic.enabled のメッセージは出力されなくなり、

f:id:ksby:20180801232830p:plain

H2 Console(http://localhost:8080/h2-console)にアクセスしても Basic 認証のダイアログは表示されませんでした。

f:id:ksby:20180801232446p:plain

gebTest を実行してみる

gebTest タスクを実行してみると、BUILD SUCCESSFUL のメッセージが出力されました。

f:id:ksby:20180802002811p:plain

ブラウザで画面を表示して入力してみる

smtp4dev を起動した後、Tomcat を起動します。http://localhost:8080/inquiry/input/01/ にアクセスして画面からデータを入力してみます。

f:id:ksby:20180802003229p:plain f:id:ksby:20180802003323p:plain f:id:ksby:20180802003412p:plain f:id:ksby:20180802003450p:plain f:id:ksby:20180802003527p:plain

完了画面まで表示できました。またメールも問題なく送信されており、

f:id:ksby:20180802003557p:plain f:id:ksby:20180802003708p:plain

DB のデータも登録されていました。

f:id:ksby:20180802004036p:plain

作成された jar ファイルの中身を確認する

boot-npm-geb-sample-1.0.2-RELEASE.jar を開いてみると BOOT-INF の下に lib ディレクトリが作成されて、その下に外部ライブラリの jar ファイルが格納されていました。1.5系の時と違い、build.gradle に implementation で依存関係を記述しても jar ファイルの生成には問題ありませんでした。

f:id:ksby:20180802004248p:plain

本番稼働用ディレクトリに jar ファイルをコピーして動作確認する

以下の手順で動作することを確認しました。

  • C:\webapps\boot-npm-geb-sample\lib の下に boot-npm-geb-sample-1.0.2-RELEASE.jar をコピーする。
  • C:\webapps\boot-npm-geb-sample\bat\webapp_startup.bat 内の環境変数 WEBAPP_JAR の値を boot-npm-geb-sample-1.0.2-RELEASE.jar に変更する。
  • サービスではなくコマンドプロンプトから webapp_startup.bat を起動する。
  • C:\webapps\boot-npm-geb-sample\logs\boot-npm-geb-sample.log の最後に Started Application ... のログが出ることを確認する。
  • http://localhost:8080/inquiry/input/01/ にアクセスして画面が表示されることを確認する。

build.gradle から runtimeOnly("org.springframework.boot:spring-boot-properties-migrator") を削除する

バージョンアップ作業が終わりましたので、build.gradle に追加していた runtimeOnly("org.springframework.boot:spring-boot-properties-migrator") を削除します。

Spring Boot 2系へのバージョンアップは以上で完了です。

メモ書き

  • Spring Boot 2系から Flyway のバージョンが 5系に上がりましたが 、Flyway はバージョンアップ前から 5系を使用していたので今回は特に何もありませんでした。
  • Spring Boot 2系は Gradle 4.x 以降のみサポートされるので、Gradle が 3.x 以前の場合には Spring Boot を 2系にバージョンアップする前に Gradle のバージョンを 4.x に上げた方がよさそうです。
  • build.gradle の書き方がいろいろ変わります。一番変更が大きいファイルでした。
  • Spring Session が地味に仕様が変わっています(build.gradle に指定する artifactId の名前が違う、hash_map がなくなった)。

もう少し続きます。FindBugs で警告が出た箇所の確認、HikariCP への変更、及び Spring Boot Actuator の導入まで試してみる予定です。

履歴

2018/08/03
初版発行。

感想

記事一覧はこちらです。

  • Javascrit の開発環境は Node.js + npm + npm-scripts + webpack + browser-sync + ESLint + Prettier + Jest の組み合わせでいいかな、と思っています。これに IntelliJ IDEA を組み合わせると実装がかなり楽になります。

  • Spring Boot のプロジェクトでも CSS フレームワークJavascript のライブラリのインストール・管理は npm でいいですね。WebJars を使用する話をたまに聞きますが、自分が使うことはないでしょう(Gradle で全部管理したいとは特に思わないため)。また npm と同じパッケージマネージャで Yarn があり、npmから乗り換えてわかったYarnの4つのメリット とかの記事を見ると Yarn いいなと思いましたが、今回はそこまで手を出しませんでした。Javascript 関連はツール類も次々と出てきて新しいものに変えると便利そうなのですが、ついていくのが大変なんですよね。。。

  • 記事を書き始めた頃にタスクランナーは gulp → npm-scripts へ切り替えよう的なものをいくつか見かけたので npm-scripts を使ってみましたが、今は npm-scripts で十分だと思っています。gulp については CSS で SASS を使うならまだ gulp の方が良さそう、複雑なタスクを書くなら gulp?、のような感じでいます。

  • webpack はとりあえず困らない程度には使えるようになったかな、という感じです(まあ Babel とかは入れていないのでそんなに使いこなしていないのではという気もしますが)。npm で Javascript のライブラリをインストールして、jQueryスクリプトを書いて1つの js ファイルにまとめるためには必須ですね。npm でインストールしたライブラリを利用する jQuery の結構サイズの大きなスクリプトを書いても、webpack でバンドル+Uglify するとかなりサイズが小さくなって軽快に動作するので便利です。また記事の途中でバージョンが 3 → 4 へ上がりましたが、今回の記事で使っている程度であればバージョンアップは難しくはありませんでした(英語の記事がいろいろ出ているのを見てからバージョンアップしましたが)。

  • ファイル変更時にブラウザを自動リロードしてくれると聞いて webpack-dev-server(今は webpack-serve) ではなく browser-sync を使ってみたのですが、これが便利でした! Javascript のファイルを変更した時に F5 を押してリロードする必要が全然ないとは驚きです。 https 環境を作りたい時も browser-sync なら設定に1行追加すればよいだけなのも便利ですね。また JRebel がないとうまく機能しませんが、browser-sync → Tomcat 連携して、html,css,js は browser-sync、それ以外は Tomcat からレスポンスを返すようにして、Java 側のモジュール変更時もリロードさせることも出来ました。1点注意があるとすれば、browser-sync で返した HTML はたまに文字化けすることがあります(今回の記事では見ませんでしたが、他で開発に使用した時に見かけました)。

  • Prettier は記事を書いている途中から使われているのを見かけるようになって、でもフォーマットは IntelliJ IDEA でやってくれるから特に導入する必要はないかなと思っていたのですが、IntelliJ IDEA でも Prettier によるフォーマットがサポートされて、Javascript では Prettier でフォーマットするのが当たり前的な記事も見かけるようになって、最後に Jest 23 の Jest Each でデータテーブルを記述する時に Prettier があればフォーマットしてくれると聞いて、導入することにしました。実際インストールしてみると Javascript のファイルを綺麗にフォーマットしてくれるし、package.json や webpack.config.js 等の設定ファイルもきれいにフォーマット出来るので、今後はインストール必須にしたいと思います。

  • ESLint は Prettier が入るとフォーマットのチェックは Prettier に依存する?ようになってチェックする内容が減りますが、フォーマット以外にもチェックして欲しい点はあるので、使った方がよいのだろうなあ、という感じです。

  • 記事を書き始めたころは Javascriptユニットテストは何を使えばよいのかよく分からず、なんか Jest が便利だよ的な記事を見かけて Jest を使ってみたのですが、今は Jest 一択です。機能が豊富でテストが書きやすく、IntelliJ IDEA でもサポートされていて実行しやすいのも魅力です。Jest 23 からは Spock のデータテーブルのような書き方もサポートされて、Parameterized test が出来るようになったのもいいですね。

  • Javascript の開発では IntelliJ IDEA で書くと便利ですね。便利と思っている点を上げてみると、

    • モジュール分割している時も Ctrl+B を押せば実装箇所にジャンプしてくれるし、Alt+F7 で使用箇所一覧が表示されます。
    • Ctrl+Shift+T を押すとモジュールに対応するユニットテストのファイルが開きます(なぜか開くんですよね、ファイル名で見ているだけけのような気がしますが)。ただし Java のように「Create New Test...」メニューは表示されません。
    • リファクタリングも動作します。変数名や関数名を変えたくらいしかしていませんが、使用している箇所も問題なく修正されました。
    • debug 実行ができます。Chrome で debug するのもいいのですが、IntelliJ IDEA で実行した方が breakpoint が設定しやすかったり、変数の値が確認しやすかったりします。
    • Jest で書いたテストの実行がサポートされていて、テストが実行しやすいです。
    • Prettier によるフォーマットもサポートされています。
    • npm-scripts の実行もサポートされています。
  • あと Javascript でやらなかったと思っているものを上げると、以下のものでしょうか。

    • TypeScript。便利なのかもしれませんが、たぶんついていけないでしょう。。。
    • Flow。型を使えるようになるので使ってみたかったのですが、途中から入れる方法がよく分からなくて諦めました。
    • ES2015(+Babel)。今回使っていませんが、無条件で使った方がよいと思っています。ES2015 の新文法がとにかく便利。
    • Vue.js、React、Angular.js 等のフレームワーク。今回は事情により jQuery をベースに npm-scripts や webpack と組み合わせた例が欲しかったので使用しませんでしたが、迷わず使うでいいと思います。Angular.js、React はついていけない気がしていて、採用するなら Vue.js かな。。。
    • Storybook。聞いただけで何に使うものか実はよく分かっていませんが、どうも便利らしい。
  • Geb が便利すぎる。。。 ブラウザの画面のテストをするなら MockMvc とかは使わずに Geb で書く方が圧倒的に楽ですね。Geb は Spock を拡張したものなので、Spock で便利と思っている機能(例えばデータテーブルによる Parameterized test)は全て使えますし、Groovy SQL も使えるので、画面の試験と合わせて DB やメールの確認も簡単に書けるんですよね。Spring Boot 2 から JUnit5 がサポートされて JUnit5 の話題が多いですが、自分は Groovy+Spock+Geb から移る気が全然起きないんですよね。。。

  • H2+Flyway を使うと1プロジェクトで DB 処理まで完結できるサンプルが作れるのは便利でした。今後は基本的にこの組み合わせでサンプルを作りたいと思います。ただし H2 は、Spring Security を導入していると管理画面にアクセスするのに少し手間がかかったり、組込モードの H2 に IntelliJ IDEA や DomaGen からアクセスするのにコツが必要だったりする点が注意ですね。

  • 見返してみると Spring Boot 自体についてはほとんど何もしていませんね。。。 Javascript 開発環境を Spring Boot プロジェクト内に一緒に入れる方法とか、Geb のテスト環境を入れる方法とか、H2+Flyway を使って別に DB サーバを構築しなくても動作確認ができるプロジェクトを作成する方法とかを調べるのが目的だったので。まあ目的は全部達成できて自分としては満足な結果です。以外に全部入れられるものでした。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その72 )( Windows で本番稼働させるためのディレクトリ作成、jar ファイル配置、bat ファイル作成、サービス登録、動作確認 )

概要

記事一覧はこちらです。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( 番外編 )( gradle-processes を利用して Geb のテスト前に Spring Boot の Web アプリを自動起動する ) の続きです。

  • 今回の手順で確認できるのは以下の内容です。
    • Windows で本番稼働させるためのディレクトリ作成、jar ファイル配置、bat ファイル作成、サービス登録、動作確認。

参照したサイト・書籍

目次

  1. application-product.properties に valueshelper.classpath.prefix の設定を追加する
  2. Windows で本番稼働させるためのディレクトリを作成する
  3. bat ファイルを作成する
  4. jar ファイルを作成、配置する
  5. nssm をダウンロード・配置し、Windows のサービスに登録する
  6. 動作確認
  7. Windows のサービスから削除する
  8. 次回は。。。

手順

application-product.properties に valueshelper.classpath.prefix の設定を追加する

ksbysample.webapp.bootnpmgeb.values.ValuesHelper クラスで使用する valueshelper.classpath.prefix の設定を記述するのを忘れていたので追加します。

src/main/resources/application-product.properties の以下の点を変更します。

..........

spring.mail.host=localhost
spring.mail.port=25

valueshelper.classpath.prefix=BOOT-INF.classes.
  • valueshelper.classpath.prefix=BOOT-INF.classes. を追加します。

Windows で本番稼働させるためのディレクトリを作成する

実行環境は以下のディレクトリ構成にします。ファイルは後から配置する ( ログファイルは後から作成される ) ので、まずは全てのディレクトリを作成します。

C:\webapps\boot-npm-geb-sample
├ bat
│ └ webapp_startup.bat
├ lib
│ └ boot-npm-geb-sample-1.0.1-RELEASE.jar
├ logs
│ ├ gc.log
│ ├ boot-npm-geb-sample.log
│ └ slow-query.log
├ nssm
│ └ nssm.exe
└ work

bat ファイルを作成する

C:\webapps\boot-npm-geb-sample\bat の下に webapp_startup.bat を新規作成し、以下の内容を記述します。

@echo on

setlocal
set JAVA_HOME=C:\Java\jdk1.8.0_172
set PATH=%JAVA_HOME%\bin;%PATH%
set WEBAPP_HOME=C:\webapps\boot-npm-geb-sample
set WEBAPP_JAR=boot-npm-geb-sample-1.0.1-RELEASE.jar
set LOGS_DIR=%WEBAPP_HOME%\logs
set PATH_HEAPDUMPFILE=%LOGS_DIR%\heapdump.hprof
set PATH_ERRORFILE=%LOGS_DIR%\hs_err.log

cd /d %WEBAPP_HOME%
java -server ^
     -Xms1024m -Xmx1024m ^
     -XX:MaxMetaspaceSize=384m ^
     -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled ^
     -XX:+UseCMSInitiatingOccupancyOnly -XX:CMSInitiatingOccupancyFraction=75 ^
     -XX:+ScavengeBeforeFullGC -XX:+CMSScavengeBeforeRemark ^
     -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps ^
     -Xloggc:%WEBAPP_HOME%/logs/gc.log ^
     -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=10M ^
     -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=%PATH_HEAPDUMPFILE% ^
     -XX:+CrashOnOutOfMemoryError ^
     -XX:ErrorFile=%PATH_ERRORFILE% ^
     -Dfile.encoding=UTF-8 ^
     -Djava.net.preferIPv4Stack=true ^
     -Dsun.net.inetaddr.ttl=100 ^
     -Dsun.nio.cs.map=x-windows-iso2022jp/ISO-2022-JP ^
     -Dcom.sun.management.jmxremote ^
     -Dcom.sun.management.jmxremote.port=7900 ^
     -Dcom.sun.management.jmxremote.ssl=false ^
     -Dcom.sun.management.jmxremote.authenticate=false ^
     -Dspring.profiles.active=product ^
     -jar lib\%WEBAPP_JAR%

set YYYYMMDD=%date:~0,4%%date:~5,2%%date:~8,2%
set HHMMSS=%time:~0,8%
set HHMMSS=%HHMMSS::=%
set HHMMSS=%HHMMSS: =%
if exist %PATH_HEAPDUMPFILE% rename %PATH_HEAPDUMPFILE% heapdump_%YYYYMMDD%%HHMMSS%.hprof
if exist %PATH_ERRORFILE% rename %PATH_ERRORFILE% hs_err_%YYYYMMDD%%HHMMSS%.log

jar ファイルを作成、配置する

clean タスク → Rebuild Project → build タスクを実行します。build/libs の下に boot-npm-geb-sample-1.0.1-RELEASE.jar を作成されますので、C:\webapps\boot-npm-geb-sample\lib の下にコピーします。

nssm をダウンロード・配置し、Windows のサービスに登録する

https://nssm.cc/download のページ内の「Latest release」の「nssm 2.24」リンクをクリックして nssm-2.24.zip をダウンロードします。

nssm-2.24.zip を解凍します。解凍して作成された nssm-2.24\win64 ( 32bit環境の場合には nssm-2.24\win32 ) ディレクトリの下にある nssm.exe を C:\webapps\boot-npm-geb-sample\nssm の下に配置します。

コマンドプロンプトを「管理者として実行...」モードで起動し、以下のコマンドを実行します。

> cd /d C:\webapps\boot-npm-geb-sample\nssm
> nssm install boot-npm-geb-sample

「NSSM service installer」画面が表示されます。以下の画像の値を入力した後、「Install service」ボタンをクリックします。サービスの登録に成功すると「Service "ksbysample-webapp-lending" installed successfully!」のダイアログが表示されますので「OK」ボタンをクリックします。

f:id:ksby:20180726004542p:plain

動作確認

最初にメールを送信できるようにするため smtp4dev を起動します。

f:id:ksby:20180726004832p:plain

サービス画面を開きます。サービス一覧から「boot-npm-geb-sample」を選択し、「サービスの開始」リンクをクリックしてサービスを開始します。

f:id:ksby:20180726005233p:plain f:id:ksby:20180726005310p:plain

C:\webapps\boot-npm-geb-sample\logs の下の boot-npm-geb-sample.log をエディタで開き、最後に "Started Application in ..." のログが出力されていることを確認します。

以下の手順で動作確認します ( 画面キャプチャは省略します )。

  • ブラウザを起動して http://localhost:8080/inquiry/input/01/ にアクセスして入力画面1を表示します。
  • 入力画面1~3に入力して確認画面を表示します。
  • 確認画面から「修正する」ボタンをクリックして入力画面1~3に戻り、入力したデータが表示されること、データを修正して確認画面に戻った場合には修正したデータが反映されることを確認します。
  • 確認画面で「送信する」ボタンをクリックして、メールが送信されること、及び完了画面が表示されることを確認します。

Windows のサービスから削除する

サービスを削除します。管理者モードで起動しているコマンドプロンプトから以下のコマンドを実行します。

> nssm remove boot-npm-geb-sample

「Remove the service?」のダイアログが表示されますので、「はい」ボタンをクリックします。サービスが削除されると「Service "boot-npm-geb-sample" removed successfully!」のダイアログが表示されますので「OK」ボタンをクリックします。

次回は。。。

感想を書いた後、この Web アプリを Spring Boot 2 へバージョンアップして、終わりにする予定です。

履歴

2018/07/29
初版発行。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( 番外編 )( gradle-processes を利用して Geb のテスト前に Spring Boot の Web アプリを自動起動する )

概要

記事一覧はこちらです。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その72 )( Executable Jar ファイルを作成しても実行できない原因を調査する+気づいた点を修正する ) の続きです。

  • 今回の手順で確認できるのは以下の内容です。
    • Geb のテストを実行する前に都度 IntelliJ IDEA から Tomcat を起動するのは手間なので自動で起動できないか調べてみたところ、gradle-processes という Gradle のプラグインを利用して実施している例を見かけました。今回番外編として試してみようと思います。
    • 尚、gradle-processes は現時点では Gradle のバージョンが 4.5 以下であることが条件となっており(実際 4.6 以上だと動きません)、今は 4.8.1 までバージョンを上げていて戻すつもりはないので今回変更した内容は commit しません。

参照したサイト・書籍

目次

  1. gradle のバージョンを 4.5 に変更する
  2. build.gradle に gradle-processes を追加する
  3. build.gradle に Web アプリの自動起動、自動停止のタスクを追加する
  4. 動作確認

手順

gradle のバージョンを 4.5 に変更する

コマンドプロンプトから gradlew wrapper --gradle-version=4.5 を実行して Gradle のバージョンを 4.5 に変更します。

f:id:ksby:20180724070816p:plain

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

..........

wrapper {
    gradleVersion = '4.5'
    distributionType = Wrapper.DistributionType.ALL
}

..........

dependencies {
    def spockVersion = "1.1-groovy-2.4"
    def domaVersion = "2.19.2"
    def lombokVersion = "1.18.0"
    def errorproneVersion = "2.3.1"
    def powermockVersion = "1.7.4"
    def seleniumVersion = "3.13.0"

    // dependency-management-plugin によりバージョン番号が自動で設定されるもの
    // Appendix F. Dependency versions ( https://docs.spring.io/spring-boot/docs/1.5.10.RELEASE/reference/html/appendix-dependency-versions.html ) 参照
    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")
    runtimeOnly("org.springframework.boot:spring-boot-devtools")
    compile("org.springframework.session:spring-session")
    compile("org.codehaus.janino:janino")
    testImplementation("org.springframework.boot:spring-boot-starter-test")
    testImplementation("org.springframework.security:spring-security-test")
    testImplementation("org.yaml:snakeyaml")
    testImplementation("org.mockito:mockito-core")

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

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

    // for Doma
//    annotationProcessor("org.seasar.doma:doma:${domaVersion}")
    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
    testImplementation("org.powermock:powermock-module-junit4:${powermockVersion}")
    testImplementation("org.powermock:powermock-api-mockito:${powermockVersion}")

    // for Geb + Spock
    testImplementation("org.gebish:geb-spock:2.1")
    testImplementation("org.seleniumhq.selenium:selenium-chrome-driver:${seleniumVersion}")
    testImplementation("org.seleniumhq.selenium:selenium-firefox-driver:${seleniumVersion}")
    testImplementation("org.seleniumhq.selenium:selenium-support:${seleniumVersion}")
    testImplementation("org.seleniumhq.selenium:selenium-api:${seleniumVersion}")
    testImplementation("org.seleniumhq.selenium:selenium-remote-driver:${seleniumVersion}")
}
  • wrapper block 内で gradleVersion = '4.8.1'gradleVersion = '4.5' に変更します。
  • dependencies block 内で annotationProcessor の行をコメントアウトします。annotationProcessor が入ったのは 4.6 からなので、4.5 では使用できません。

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

build.gradle に gradle-processes を追加する

追加する方法が少し分かりづらかったです。。。

Maven Repository で "gradle-processes" で検索すると、以下の結果が表示されます。最初の2件がどちらも gradle-processes です。

f:id:ksby:20180724071704p:plain

1件目をクリックすると以下の画面が表示されます。

f:id:ksby:20180724071909p:plain

2件目をクリックすると以下の画面が表示されます。

f:id:ksby:20180724072047p:plain f:id:ksby:20180724072235p:plain

また gradle-processes の GitHub のページ を見ると How To Use に以下の記述があります。

f:id:ksby:20180724072452p:plain

以上の内容から、利用するためには以下の設定が必要なことが分かります。

  • gradle-processes を利用するにはレポジトリに Gradle Plugins repository(https://plugins.gradle.org/m2/)を追加する必要がある。
  • 最新バージョンは 0.4.1。plugin block に id "com.github.johnrengelman.processes" version "0.4.1" の記述を追加する。

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

buildscript {
    ext {
        group "ksbysample"
        version "1.0.1-RELEASE"
        springBootVersion = "1.5.14.RELEASE"
    }
    repositories {
        mavenCentral()
        maven { url "https://plugins.gradle.org/m2/" }
    }
}

plugins {
    id "java"
    id "eclipse"
    id "idea"
    // plugins {} block 内では ${springBootVersion} が使用できないので、バージョンを直接記述している
    id "org.springframework.boot" version "1.5.14.RELEASE"
    id "groovy"
    id "net.ltgt.errorprone" version "0.0.14"
    id "checkstyle"
    id "findbugs"
    id "pmd"
    id "com.moowork.node" version "1.2.0"
    id "com.github.johnrengelman.processes" version "0.4.1"
}

..........
  • buildscript block に repositories { ... } を追加します。この中で maven { url "https://plugins.gradle.org/m2/" } を記述します。
  • plugins block に id "com.github.johnrengelman.processes" version "0.4.1" を追加します。version "0.4.1" の記述がないと Plugin [id: 'com.github.johnrengelman.processes'] was not found in any of the following sources: ... - Plugin Repositories (plugin dependency must include a version number for this source) というエラーが出ます。

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

build.gradle に Web アプリの自動起動、自動停止のタスクを追加する

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

buildscript {
    ext {
        group "ksbysample"
        version "1.0.1-RELEASE"
        springBootVersion = "1.5.14.RELEASE"
        mainClass = "ksbysample.webapp.bootnpmgeb.Application"
    }
    repositories {
        mavenCentral()
        maven { url "https://plugins.gradle.org/m2/" }
    }
}

..........

bootRepackage {
    mainClass = "${mainClass}"
}

..........

// for Geb + Spock Integration Test
task startServer(type: com.github.jengelman.gradle.plugins.processes.tasks.JavaFork) {
    jvmArgs = [
            '-Dspring.profiles.active=develop',
            '-Dlogging.level.root=OFF',
            '-Dlogging.level.org.springframework.web=OFF',
            '-Dlogging.level.jdbc.sqlonly=OFF',
            '-Dlogging.level.jdbc.sqltiming=OFF',
            '-Dlogging.level.jdbc.audit=OFF',
            '-Dlogging.level.jdbc.resultset=OFF',
            '-Dlogging.level.jdbc.resultsettable=OFF',
            '-Dlogging.level.jdbc.connection=OFF'
    ]
    classpath += sourceSets.main.runtimeClasspath
    main = "${mainClass}"
    doLast {
        Thread.sleep(15000)
    }
}
task stopServer {
    doLast {
        startServer.processHandle.abort()
    }
}
def drivers = ["chrome", "firefox"]
drivers.each { driver ->
    task "${driver}Test"(type: Test) {
        // 前回実行時以降に何も更新されていなくても必ず実行する
        outputs.upToDateWhen { false }
        systemProperty "geb.env", driver
        exclude "ksbysample/**"
        dependsOn startServer
        finalizedBy stopServer
    }
}
task gebTest {
    dependsOn drivers.collect { tasks["${it}Test"] }
    enabled = false
}

..........
  • buildscript block 内の ext block 内に mainClass = "ksbysample.webapp.bootnpmgeb.Application" を追加します。メインクラスを記述する箇所が2箇所になるので、共通で使用する変数を定義します。
  • bootRepackage block 内で mainClass = '${ksbysample.webapp.bootnpmgeb.Application}'mainClass = "${mainClass}" に変更します。
  • 以下の2つの task を追加します。gradle-processes では task startServer(type: Fork) のように書かれていますが、なぜか動作しなかったので com.github.jengelman.gradle.plugins.processes.tasks.JavaFork のようにパッケージも含めて記述しています。
    • task startServer(type: com.github.jengelman.gradle.plugins.processes.tasks.JavaFork) { ... }
    • task stopServer { ... }
  • drivers.each { driver -> task "${driver}Test"(type: Test) { ... } } 内に以下の2行を追加します。
    • dependsOn startServer
    • finalizedBy stopServer

動作確認

最初に clean タスク → Rebuild Project → build タスクを実行して BUILD SUCCESSFUL が表示されること、startServer と stopServer タスクが実行されないことを確認します。

f:id:ksby:20180725133039p:plain

なぜかこのタイミングで Chrome 68 が Twitter のトレンドにランクインしていたので、バージョンアップします。

次に gebTest タスクを実行します。startServer → chromeTest, firefoxTest → stopServer の順に実行されて、BUILD SUCCESSFUL が表示されました。問題なさそうです。

f:id:ksby:20180725134247p:plain

chromeTest タスクだけを実行してみます。startServer → chromeTest → stopServer の順に実行されて、テストは全て成功しました。

f:id:ksby:20180725134745p:plain

firefoxTest タスクだけを実行してみます。startServer → firefoxTest → stopServer の順に実行されて、テストは全て成功しました。

f:id:ksby:20180725140030p:plain

このプラグイン、便利ですね。GitHub の Issue を見たら現在 4.6 以降に対応中のようです。

履歴

2018/07/25
初版発行。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その72 )( Executable Jar ファイルを作成しても実行できない原因を調査する+気づいた点を修正する )

概要

記事一覧はこちらです。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その71 )( Geb で入力画面1~3→確認画面→完了画面を通したテストを作成する ) の続きです。

  • 今回の手順で確認できるのは以下の内容です。
    • 実装すべきことが全て完了したので jar ファイルを作成して実行できることを確認しようとしたのですが、なぜか実行できませんでした。原因を調査します。
    • 他に何点か気づいた点を修正します。

参照したサイト・書籍

目次

  1. Executable Jar ファイルを作成しても実行できない原因を調査する
    1. どのような問題が発生しているのか?
    2. build.gradle の mainClass を修正して再度実行する
    3. java.lang.ClassNotFoundException: org.springframework.boot.SpringApplication の原因とは?
    4. build.gradle の dependencies で implementationcompile に戻してみる
    5. Spring Initializr を利用して Spring Boot 2.0.3 のプロジェクトを作って検証する
    6. Spring Initializr を利用して Spring Boot 1.5.14 のプロジェクトを作って検証する
    7. 結論
    8. build.gradle を変更する+動作確認
  2. 気づいた点を修正する
    1. PowerMockito#verifyStatic(VerificationMode) が deprecated になっていたので PowerMockito#verifyStatic(Class, VerificationMode) に変更する

手順

Executable Jar ファイルを作成しても実行できない原因を調査する

どのような問題が発生しているのか?

clean タスク実行 → Rebuild Project 実行 → build タスクを実行して、build/libs の下に boot-npm-geb-sample-1.0.1-RELEASE.jar を作成します。

f:id:ksby:20180722190850p:plain f:id:ksby:20180722190944p:plain

コマンドプロンプトから java -Dspring.profiles.active=develop -jar build\libs\boot-npm-geb-sample-1.0.1-RELEASE.jar を実行すると Exception in thread "main" java.lang.ClassNotFoundException: ksbysample.webapp.lending.Application というエラーメッセージが出力されて実行できませんでした。Application クラスのパッケージが lending になっているということは、build.gradle を修正するのを忘れていますね。。。

f:id:ksby:20180722191212p:plain

build.gradle の mainClass を修正して再度実行する

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

bootRepackage {
    mainClass = 'ksbysample.webapp.bootnpmgeb.Application'
}
  • bootRepackage の mainClass の設定を ksbysample.webapp.lending.Applicationksbysample.webapp.bootnpmgeb.Application に変更します。

jar ファイルを作成し直してから java -Dspring.profiles.active=develop -jar build\libs\boot-npm-geb-sample-1.0.1-RELEASE.jar を実行すると、今度は java.lang.ClassNotFoundException: org.springframework.boot.SpringApplication のエラーメッセージが出力されて実行できません。

f:id:ksby:20180722192336p:plain

java.lang.ClassNotFoundException: org.springframework.boot.SpringApplication の原因とは?

Spring Boot の jar ファイルが Executable Jar ファイル内に出力されていない? ExecutableJar ファイルの中を見てみると、BOOT-INF の下に lib ディレクトリが存在しないことが分かりました。

boot-npm-geb-sample-1.0.1-RELEASE.jar の BOOT-INF の下を見ると、以下のように lib ディレクトリがありませんが、

f:id:ksby:20180722193119p:plain

java -jar コマンドで実行できる ksbysample-webapp-lending の ksbysample-webapp-lending-1.5.4-RELEASE.jar を開いてみると、BOOT-INF/lib ディレクトリが存在し、その下に jar ファイルが格納されています。

f:id:ksby:20180722192819p:plain

build.gradle の dependencies で implementationcompile に戻してみる

原因がよく分からなかったのですが、build.gradle を見ていて、そう言えば Spring Boot + npm + Geb で入力フォームを作ってテストする ( その65 )( Gradle を 4.6 → 4.8.1 へ、Checkstyle を 8.8 → 8.11 へ、PMD を 6.4.0 → 6.5.0 へ、error-prone を 2.2.0 → 2.3.1 へバージョンアップする ) で依存関係の記述の仕方を compileimplementation に変更したことを思い出しました。これが原因のような気がします。一旦 compile に戻してみます。

build.gradle で1つだけ implementationcompile に戻してみます。

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

    // dependency-management-plugin によりバージョン番号が自動で設定されるもの
    // Appendix F. Dependency versions ( https://docs.spring.io/spring-boot/docs/1.5.10.RELEASE/reference/html/appendix-dependency-versions.html ) 参照
    compile("org.springframework.boot:spring-boot-starter-web")
    implementation("org.springframework.boot:spring-boot-starter-thymeleaf") {
        exclude group: "org.codehaus.groovy", module: "groovy"
    }

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

jar ファイルを作成し直して開いてみると、今度は BOOT-INF/lib ディレクトリが存在し、その下に jar ファイルが格納されていました。

f:id:ksby:20180722194853p:plain

ということは、Spring Boot Gradle Plugin が implementation に対応していないということでしょうか。。。

Spring Initializr を利用して Spring Boot 2.0.3 のプロジェクトを作って検証する

Spring Initializr を利用してプロジェクトを作成して、build.gradle に implementation で記述していると Executable Jar ファイルを作成した時に BOOT-INF/libs を作成してくれるのか否かを検証してみます。まずは 2系の最新バージョンである 2.0.3 から。

IntelliJ IDEA で Spring Initializr を利用してプロジェクトを作成します。

f:id:ksby:20180722203303p:plain f:id:ksby:20180722203353p:plain f:id:ksby:20180722203446p:plain f:id:ksby:20180722203541p:plain f:id:ksby:20180722203659p:plain

プロジェクトが作成されたら build.gradle を開き、compileimplementationruntimeruntimeOnlytestCompiletestImplementation に変更します。あと devtools が runtime で記述されていることに気づきました。あとで反映しましょう。

buildscript {
    ext {
        springBootVersion = '2.0.3.RELEASE'
    }
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }
}

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8

repositories {
    mavenCentral()
}


dependencies {
    implementation('org.springframework.boot:spring-boot-starter-web')
    runtimeOnly('org.springframework.boot:spring-boot-devtools')
    testImplementation('org.springframework.boot:spring-boot-starter-test')
}

Gradle Tool Window の左上にある「Refresh all Gradle projects」ボタンをクリックして更新した後、build タスクを実行して jar ファイルを作成します。2系から demo-0.0.1-SNAPSHOT.jar.original って出来なくなったんですね。

f:id:ksby:20180722204209p:plain

作成された jar ファイルを開くと BOOT-INF/lib ディレクトリが存在し、その下に jar ファイルが出力されていますね。。。?

f:id:ksby:20180722204501p:plain

Spring Initializr を利用して Spring Boot 1.5.14 のプロジェクトを作って検証する

1.5.14 のプロジェクトも作成してみます。

IntelliJ IDEA で Spring Initializr を利用してプロジェクトを作成します。

f:id:ksby:20180722204831p:plain f:id:ksby:20180722204925p:plain f:id:ksby:20180722205019p:plain

プロジェクトが作成されたら build.gradle を開き、compileimplementationruntimeruntimeOnlytestCompiletestImplementation に変更します。

buildscript {
    ext {
        springBootVersion = '1.5.14.RELEASE'
    }
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }
}

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8

repositories {
    mavenCentral()
}


dependencies {
    implementation('org.springframework.boot:spring-boot-starter-web')
    runtimeOnly('org.springframework.boot:spring-boot-devtools')
    testImplementation('org.springframework.boot:spring-boot-starter-test')
}

Gradle Tool Window の左上にある「Refresh all Gradle projects」ボタンをクリックして更新した後、build タスクを実行して jar ファイルを作成します。

f:id:ksby:20180722210459p:plain

作成された jar ファイルを開くと BOOT-INF/lib ディレクトリが存在しませんでした。

f:id:ksby:20180722211758p:plain

結論

  • 2系の Spring Boot Gradle Plugin は build.gradle の implementation に対応しているが、1系の Spring Boot Gradle Plugin は対応していない。
  • 1系で build.gradle の dependencies に compile ではなく implementation で記述すると、Executable Jar ファイルを作成した時に BOOT-INF/lib ディレクトリが作成されず外部モジュールの jar ファイルが格納されないため、java -jar コマンドで実行しようとしても実行できない。

build.gradle を変更する+動作確認

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

dependencies {
    def spockVersion = "1.1-groovy-2.4"
    def domaVersion = "2.19.2"
    def lombokVersion = "1.18.0"
    def errorproneVersion = "2.3.1"
    def powermockVersion = "1.7.4"
    def seleniumVersion = "3.13.0"

    // dependency-management-plugin によりバージョン番号が自動で設定されるもの
    // Appendix F. Dependency versions ( https://docs.spring.io/spring-boot/docs/1.5.10.RELEASE/reference/html/appendix-dependency-versions.html ) 参照
    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")
    runtimeOnly("org.springframework.boot:spring-boot-devtools")
    compile("org.springframework.session:spring-session")
    compile("org.codehaus.janino:janino")
    testImplementation("org.springframework.boot:spring-boot-starter-test")
    testImplementation("org.springframework.security:spring-security-test")
    testImplementation("org.yaml:snakeyaml")
    testImplementation("org.mockito:mockito-core")

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

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

    // for Doma
    annotationProcessor("org.seasar.doma:doma:${domaVersion}")
    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
    testImplementation("org.powermock:powermock-module-junit4:${powermockVersion}")
    testImplementation("org.powermock:powermock-api-mockito:${powermockVersion}")

    // for Geb + Spock
    testImplementation("org.gebish:geb-spock:2.1")
    testImplementation("org.seleniumhq.selenium:selenium-chrome-driver:${seleniumVersion}")
    testImplementation("org.seleniumhq.selenium:selenium-firefox-driver:${seleniumVersion}")
    testImplementation("org.seleniumhq.selenium:selenium-support:${seleniumVersion}")
    testImplementation("org.seleniumhq.selenium:selenium-api:${seleniumVersion}")
    testImplementation("org.seleniumhq.selenium:selenium-remote-driver:${seleniumVersion}")
}
  • implementationcompile に戻します。
  • compile("org.springframework.boot:spring-boot-devtools")runtimeOnly("org.springframework.boot:spring-boot-devtools") に変更します。

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

clean タスク実行 → Rebuild Project 実行 → build タスクを実行して、build/libs の下に boot-npm-geb-sample-1.0.1-RELEASE.jar を作成します。boot-npm-geb-sample-1.0.1-RELEASE.jar を開くと BOOT-INF/lib ディレクトリが存在し jar ファイルが格納されていました。

f:id:ksby:20180722224047p:plain

コマンドプロンプトから java -Dspring.profiles.active=develop -jar build\libs\boot-npm-geb-sample-1.0.1-RELEASE.jar を実行すると今度は正常に起動できました。

f:id:ksby:20180722224542p:plain f:id:ksby:20180722224831p:plain

気づいた点を修正する

PowerMockito#verifyStatic(VerificationMode) が deprecated になっていたので PowerMockito#verifyStatic(Class, VerificationMode) に変更する

見つけたのは偶然ですが、InquiryInput02FormValidatorTest クラスの "メールアドレスの Validation のテスト"() テストメソッドで PowerMockito#verifyStatic(VerificationMode) を呼び出しているところに取り消し線が引かれていました。deprecated になっており、PowerMockito#verifyStatic(Class, VerificationMode) に変更するよう PowerMockito#verifyStatic(VerificationMode)JavaDoc に記述されていましたので、変更します。

src/test/groovy/ksbysample/webapp/bootnpmgeb/web/inquiry/form/InquiryInput02FormValidatorTest.groovy の以下の点を変更します。

        @Test
        void "メールアドレスの Validation のテスト"() {
            ..........

            then: "入力チェックエラーは発生しない"
            assert errors.hasErrors() == false
            assert errors.getAllErrors().size() == 0
            // EmailValidator.validate が呼び出されていることをチェックする
            PowerMockito.verifyStatic(EmailValidator, Mockito.times(1))
            EmailValidator.validate("taro.tanaka@sample.co.jp")

            ..........
        }
  • PowerMockito.verifyStatic(Mockito.times(1))PowerMockito.verifyStatic(EmailValidator, Mockito.times(1)) に変更します。

履歴

2018/07/23
初版発行。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その71 )( Geb で入力画面1~3→確認画面→完了画面を通したテストを作成する )

概要

記事一覧はこちらです。

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

  • 今回の手順で確認できるのは以下の内容です。
    • Geb を利用して、入力画面1~3→確認画面→完了画面を通したテストを作成します。
    • テストでは DB のデータやメールの送信内容も確認します。

参照したサイト・書籍

目次

  1. InquiryInput01Page クラスを変更する
  2. InquiryInput02Page クラスを変更する
  3. InquiryInput03Page クラスを作成する
  4. InquiryConfirmPage クラスを作成する
  5. InquiryCompletePage クラスを作成する
  6. FormModule クラスを変更する
  7. inquirymail-body_003.txt を追加する
  8. InquiryTestSpec クラスに入力画面1~3→確認画面→完了画面を通したテストを追加する
  9. 動作確認
  10. 全ての Geb のテストを実行してみる

手順

InquiryInput01Page クラスを変更する

src/test/groovy/geb/page/inquiry/InquiryInput01Page.groovy に今回使用するテストデータを追加します。

class InquiryInput01Page extends Page {
    ..........

    static valueList01 = [
            "#lastname"        : "田中",
            "#firstname"       : "太郎",
            "#lastkana"        : "たなか",
            "#firstkana"       : "たろう",
            "input[name='sex']": SexValues.MALE.value,
            "#age"             : "30",
            "#job"             : JobValues.EMPLOYEE.value,
    ]

}
  • static valueList01 = [ ... ] を追加します。

InquiryInput02Page クラスを変更する

src/test/groovy/geb/page/inquiry/InquiryInput02Page.groovy に今回使用するテストデータを追加します。

class InquiryInput02Page extends Page {
    ..........

    static valueList01 = [
            "#zipcode1": "100",
            "#zipcode2": "0005",
            "#address" : "東京都千代田区飯田橋1-1",
            "#tel1"    : "03",
            "#tel2"    : "1234",
            "#tel3"    : "5678",
            "#email"   : "taro.tanaka@sample.co.jp",
    ]

}

InquiryInput03Page クラスを作成する

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

package geb.page.inquiry

import geb.Page
import geb.module.FormModule
import ksbysample.webapp.bootnpmgeb.values.Type1Values
import ksbysample.webapp.bootnpmgeb.values.Type2Values

class InquiryInput03Page extends Page {

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

    static initialValueList = [
            "#type1"              : "",
            "input[name='type2']" : null,
            "#inquiry"            : "",
            "input[name='survey']": null,
    ]

    static valueList01 = [
            "#type1"              : Type1Values.PRODUCT.value,
            "input[name='type2']" : [
                    Type2Values.CATALOGUE.value,
                    Type2Values.ESTIMATE.value,
                    Type2Values.OTHER.value,
            ],
            "#inquiry"            : "これは\nテスト\nです",
            "input[name='survey']": ["1", "2", "3", "4", "5", "6", "7", "8"],
    ]

}

InquiryConfirmPage クラスを作成する

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

package geb.page.inquiry

import geb.Page
import geb.module.FormModule
import ksbysample.webapp.bootnpmgeb.values.JobValues
import ksbysample.webapp.bootnpmgeb.values.SexValues
import ksbysample.webapp.bootnpmgeb.values.Type1Values
import ksbysample.webapp.bootnpmgeb.values.Type2Values

import java.util.stream.Collectors

class InquiryConfirmPage extends Page {

    static url = "/inquiry/confirm"
    static at = { title == "入力フォーム - 確認画面" }
    static content = {
        form { module FormModule }
    }

    static textList01 = [
            "#name"   : "田中 太郎",
            "#kana"   : "たなか たろう",
            "#sex"    : SexValues.MALE.text,
            "#age"    : "30 歳",
            "#job"    : JobValues.EMPLOYEE.text,
            "#zipcode": "〒 100-0005",
            "#address": "東京都千代田区飯田橋1-1",
            "#tel"    : "03-1234-5678",
            "#email"  : "taro.tanaka@sample.co.jp",
            "#type1"  : Type1Values.PRODUCT.text,
            "#type2"  : [Type2Values.ESTIMATE.text
                         , Type2Values.CATALOGUE.text
                         , Type2Values.OTHER.text].stream()
                    .collect(Collectors.joining("、")),
            "#inquiry": "これは\nテスト\nです",
    ]

}

InquiryCompletePage クラスを作成する

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

package geb.page.inquiry

import geb.Page
import geb.module.FormModule
import org.openqa.selenium.By

class InquiryCompletePage extends Page {

    static url = "/inquiry/confirm"
    static at = { title == "入力フォーム - 完了画面" }
    static content = {
        form { module FormModule }
        btnToInput01 { $(By.xpath("//button[contains(text(), '入力画面へ')]")) }
    }

}

FormModule クラスを変更する

src/test/groovy/geb/module/FormModule.groovy の以下の点を変更します。

class FormModule extends Module {

    static content = {
        btnBack { $(".js-btn-back") }
        btnNext { $(".js-btn-next") }
        btnConfirm { $(".js-btn-confirm") }
        btnInput01 { $(".js-btn-input01") }
        btnSend { $(".js-btn-send") }
    }

    ..........

    /**
     * セレクタに値がセットされているかを検証する
     * このメソッドは 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 if ($(key).first().attr("type") == "checkbox") {
                value.each { v ->
                    WebElement element = $(key).allElements().find {
                        el -> el.getAttribute("value") == v && el.selected
                    }
                    assert element != null
                }
            } else {
                assert $(key).value() == value
            }
        }
        true
    }

    /**
     * セレクタにテキストがセットされているかを検証する
     * このメソッドは Spock の then, expect で使用する想定である
     *
     * @param textList 検証するセレクタと値を記述した Map
     * @return true 固定
     */
    boolean assertTextList(textList) {
        textList.each { key, value ->
            assert $(key).text() == value
        }
        true
    }

}
  • static content = { ... } 内に以下のボタンの定義を追加します。
    • btnConfirm
    • btnInput01
    • btnSend
  • assertValueList メソッド内に else if ($(key).first().attr("type") == "checkbox") { ... } の処理を追加します。
  • assertTextList メソッドを追加します。

inquirymail-body_003.txt を追加する

メールの本文検証用のファイルを追加します。src/test/resources/ksbysample/webapp/bootnpmgeb/web/inquiry の下に inquirymail-body_003.txt を追加し、以下の内容を記述します。

問い合わせフォームから入力された内容は以下の通りです。

お名前(漢字):田中 太郎
お名前(かな):たなか たろう
性別:男性
年齢:30歳
職業:会社員
郵便番号:〒100-0005
住所:東京都千代田区飯田橋1-1
電話番号:03-1234-5678
メールアドレス:taro.tanaka@sample.co.jp

お問い合わせの種類1:製品に関するお問い合わせ
お問い合わせの種類2:見積が欲しい、資料が欲しい、その他の問い合わせ
お問い合わせの内容:
これは
テスト
です
アンケート:
・選択肢1だけ長くしてみる
・選択肢2
・選択肢3
・選択肢4
・選択肢5が少し長い
・選択肢6
・選択肢7
・8

InquiryTestSpec クラスに入力画面1~3→確認画面→完了画面を通したテストを追加する

src/test/groovy/geb/gebspec/inquiry/InquiryTestSpec.groovy の以下の点を変更します。

class InquiryTestSpec extends GebSpec {

    @Rule
    MailServerResource mailServerResource = new MailServerResource()

    Sql sql

    def setup() {
        // 外部プロセスから接続するので H2 TCP サーバへ接続する
        sql = Sql.newInstance("jdbc:h2:tcp://localhost:9092/mem:bootnpmgebdb", "sa", "")
        sql.execute("truncate table INQUIRY_DATA")
    }

    def teardown() {
        sql.close()
    }

    ..........

    def "入力画面1~3→確認画面→完了画面の全ての画面を通す"() {
        setup: "入力画面1を表示する"
        to InquiryInput01Page

        and: "データを入力して次へボタンをクリックし、入力画面2へ遷移する"
        form.setValueList(valueList01)
        form.btnNext.click(InquiryInput02Page)

        and: "データを入力して次へボタンをクリックし、入力画面3へ遷移する"
        form.setValueList(valueList01)
        form.btnNext.click(InquiryInput03Page)

        and: "データを入力して次へボタンをクリックし、確認画面へ遷移する"
        form.setValueList(valueList01)
        form.btnConfirm.click(InquiryConfirmPage)

        and: "確認画面にデータが表示されていることを確認する"
        form.assertTextList(textList01)
        $("#survey > ul > li").count(8)
        $("#survey > ul > li", 0).text() == "選択肢1だけ長くしてみる"
        $("#survey > ul > li", 7).text() == "8"

        and: "修正するボタンをクリックし、入力画面1へ戻る"
        form.btnInput01.click(InquiryInput01Page)
        form.assertValueList(valueList01)

        and: "次へボタンをクリックし、入力画面2へ遷移する"
        form.btnNext.click(InquiryInput02Page)
        form.assertValueList(valueList01)

        and: "次へボタンをクリックし、入力画面3へ遷移する"
        form.btnNext.click(InquiryInput03Page)
        form.assertValueList(valueList01)

        and: "次へボタンをクリックし、確認画面へ遷移する"
        form.btnConfirm.click(InquiryConfirmPage)
        form.assertTextList(textList01)
        $("#survey > ul > li").count(8)
        $("#survey > ul > li", 0).text() == "選択肢1だけ長くしてみる"
        $("#survey > ul > li", 7).text() == "8"

        expect: "送信するボタンをクリックし、完了画面へ遷移する"
        form.btnSend.click(InquiryCompletePage)

        // INQUIRY_DATA テーブルに1件データが登録されていることを確認する
        def rows = sql.rows("SELECT * FROM INQUIRY_DATA")
        rows.size() == 1
        rows[0]["lastname"] == "田中"
        rows[0]["firstname"] == "太郎"
        rows[0]["lastkana"] == "たなか"
        rows[0]["firstkana"] == "たろう"
        rows[0]["sex"] == SexValues.MALE.value
        rows[0]["age"] == 30
        rows[0]["job"] == JobValues.EMPLOYEE.value
        rows[0]["zipcode1"] == "100"
        rows[0]["zipcode2"] == "0005"
        rows[0]["address"] == "東京都千代田区飯田橋1-1"
        rows[0]["tel1"] == "03"
        rows[0]["tel2"] == "1234"
        rows[0]["tel3"] == "5678"
        rows[0]["email"] == "taro.tanaka@sample.co.jp"
        rows[0]["type1"] == Type1Values.PRODUCT.value
        rows[0]["type2"] == [Type2Values.ESTIMATE.value
                             , Type2Values.CATALOGUE.value
                             , Type2Values.OTHER.value].stream()
                .collect(Collectors.joining(","))
        rows[0]["inquiry"].asciiStream.text == "これは\r\nテスト\r\nです"
        rows[0]["survey"] == "1,2,3,4,5,6,7,8"

        // メールが1件送信されていることを確認する
        mailServerResource.messagesCount == 1
        MimeMessage message = mailServerResource.firstMessage
        message.subject == "問い合わせフォームからお問い合わせがありました"
        message.content == new ClassPathResource("ksbysample/webapp/bootnpmgeb/web/inquiry/inquirymail-body_003.txt").inputStream.text

        and: "再び入力画面1を表示する"
        btnToInput01.click(InquiryInput01Page)
        // 入力したデータは表示されていない
        form.assertValueList(initialValueList)
    }

}
  • 以下のフィールドを追加する。
    • @Rule MailServerResource mailServerResource = new MailServerResource()
    • Sql sql
  • setup()teardown() を追加する。
  • def "入力画面1~3→確認画面→完了画面の全ての画面を通す"() { ... } を追加する。

動作確認

まずは Firefox でテストします。GebConfig.groovy は以下の設定です。

System.setProperty("webdriver.gecko.driver", "C:/geckodriver/0.19.0/geckodriver.bat")
System.setProperty("webdriver.chrome.driver", "C:/chromedriver/2.33/chromedriver.exe")
driver = {
    FirefoxOptions firefoxOptions = new FirefoxOptions()
    firefoxOptions.setHeadless(true)
    new FirefoxDriver(firefoxOptions)
}

Tomcat を起動した後、src/test/groovy/geb/gebspec/inquiry/InquiryTestSpec.groovy の "入力画面1~3→確認画面→完了画面の全ての画面を通す"() テストメソッド左側の矢印から Run '入力画面1~3→確認画面→完了画面の...()' をクリックします。

f:id:ksby:20180722015332p:plain

テストが実行されて成功しました。

f:id:ksby:20180722020322p:plain

次は Chrome でテストするために、GebConfig.groovy を以下のように変更します。

System.setProperty("webdriver.gecko.driver", "C:/geckodriver/0.19.0/geckodriver.bat")
System.setProperty("webdriver.chrome.driver", "C:/chromedriver/2.33/chromedriver.exe")
driver = {
//    FirefoxOptions firefoxOptions = new FirefoxOptions()
//    firefoxOptions.setHeadless(true)
//    new FirefoxDriver(firefoxOptions)
    ChromeOptions chromeOptions = new ChromeOptions()
    chromeOptions.setHeadless(true)
    new ChromeDriver(chromeOptions)
}

Firefox の時と同様に Run '入力画面1~3→確認画面→完了画面の...()' をクリックすると、テストは実行されましたが今度は失敗しました。入力項目に値がセットできていないようです。

f:id:ksby:20180722020624p:plain

ChromeDrive の Downloads ページ を見ると 2.40 がリリースされていましたので入れ替えます。Supports Chrome v66-68 のようにサポートしている Chrome のバージョンが記載されており、2.33 だと Supports Chrome v60-62 で使用している Chrome とバージョンが合っていませんでした(現時点の Chrome のバージョンは 67)。

ChromeDriver 2.40 のリンクをクリックして、その先にある chromedriver_win32.zip をダウンロードします。ダウンロード後、解凍して作成された chromedriver.exe を C:\chromedriver\2.40 の下に配置します。

GebConfig.groovy の chromedriver.exe のパスを変更してから、

System.setProperty("webdriver.gecko.driver", "C:/geckodriver/0.19.0/geckodriver.bat")
System.setProperty("webdriver.chrome.driver", "C:/chromedriver/2.40/chromedriver.exe")

テストを実行すると今度は成功しました。

f:id:ksby:20180722021646p:plain

全ての Geb のテストを実行してみる

今度は Geb の全てのテストを実行してみます。Gradle Tool Window から firefoxTest タスクを実行します。

f:id:ksby:20180722090807p:plain

半数以上失敗しましたね。。。 失敗した原因を確認したところ、以下の内容でした。

  • InquiryInput01Page クラスの maxLengthValueList に全ての項目が記述されていないので、値がセットされない入力項目がある。
  • InquiryInput01Page クラスの maxLengthValueList で "#lastkana""#firstkana" にカナの値が設定されていなかったので、入力チェックエラーが発生していた。

src/test/groovy/geb/page/inquiry/InquiryInput01Page.groovy の maxLengthValueList を以下のように変更します。

    static maxLengthValueList = [
            "#lastname"        : "田" * 20,
            "#firstname"       : "太" * 20,
            "#lastkana"        : "あ" * 20,
            "#firstkana"       : "い" * 20,
            "input[name='sex']": SexValues.MALE.value,
            "#age"             : "9" * 3,
            "#job"             : JobValues.EMPLOYEE.value,
    ]

また maxLengthValueList で全ての値をセットするように変更したので、src/test/groovy/geb/gebspec/inquiry/InquiryTestSpec.groovy から以下の記述の行を全て取り除きます。

  • form.setValue("input[name='sex']", "1")
  • $("#inquiryInput01Form").sex == "1"

再度 firefoxTest タスクを実行すると今度は全て成功しました。

f:id:ksby:20180722103908p:plain

次に chromeTest タスクを実行してみると、こちらも全て成功しました。

f:id:ksby:20180722104232p:plain

最後に gebTest タスクを実行して全て成功することを確認します。

f:id:ksby:20180722104913p:plain

履歴

2018/07/22
初版発行。
2018/07/22
* Geb のテストのために Spring Boot のアプリを起動しているのは develop profile で unittest profile でないことに気づいたのと、unittest profile でも H2 TCP サーバを起動させると一部のテストが失敗するようになったので、「H2DatabaseConfig クラスを変更し、unittest profile でも H2 TCPサーバを起動させる」を削除しました。