かんがるーさんの日記

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

IntelliJ IDEA を 2017.2.6 → 2017.3.1 へバージョンアップ

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

IntelliJ IDEA の 2017.3 が少し前にリリースされましたが、HTML か JS のフォーマットが崩れたので 2017.2 に戻したという記事を見かけたのでバージョンアップしないでいました。2017.3.1 がリリースされて、さすがに解消されていると思うのでバージョンアップします。

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

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

  2. IDE and Plugin Updates」ダイアログが表示されます。今回は左下に「Update and Restart」ボタンが表示されていないので、

    f:id:ksby:20171216200358p:plain

    「Download」ボタンを押して IntelliJ IDEA の Download ページを開いた後、「DOWNLOAD」ボタンを押して ideaIU-2017.3.1.exe をダウンロードします。

    f:id:ksby:20171216200613p:plain

  3. 起動している IntelliJ IDEA を終了します。

  4. ideaIU-2017.3.1.exe を実行します。

  5. IntelliJ IDEA Setup」ダイアログが表示されます。「Next >」ボタンをクリックします。

    f:id:ksby:20171216203658p:plain

  6. 「Uninstall old versions」画面が表示されます。画面上の全てのチェックボックスをチェックした後、「Next >」ボタンをクリックします。

    f:id:ksby:20171216203849p:plain

  7. 「Choose Install Location」画面が表示されます。「Destination Folder」を C:\IntelliJ_IDEA\2017.3.1 に変更した後、「Next >」ボタンをクリックします。

    f:id:ksby:20171216204055p:plain

  8. 「Installation Options」画面が表示されます。何も変更せずに「Next >」ボタンをクリックします。

    f:id:ksby:20171216204149p:plain

  9. 「Choose Start Menu Folder」画面が表示されます。何も変更せずに「Install」ボタンをクリックします。

    f:id:ksby:20171216204259p:plain

  10. 「Installing」画面が表示されてインストールが始まりますので、完了するまで待ちます。

  11. インストールが完了すると「Completing IntelliJ IDEA Setup」画面が表示されます。「Finish」ボタンをクリックしてダイアログを閉じます。

    f:id:ksby:20171216204653p:plain

  12. C:\IntelliJ_IDEA\2017.2 ディレクトリが残っているので削除します。

  13. C:\IntelliJ_IDEA\2017.3.1\bin\idea64.exe を実行します。

  14. 「Complete Installation」ダイアログが表示されます。何も変更せずに「OK」ボタンをクリックします。

    f:id:ksby:20171216204945p:plain

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

    f:id:ksby:20171216205232p:plain

  16. 「Indexing…」のメッセージが消えると「Project code style settings migration」というダイアログが表示されました。

    f:id:ksby:20171216205710p:plain

    ダイアログ左下の「More info」のリンクをクリックすると https://confluence.jetbrains.com/display/IDEADEV/New+project+code+style+settings+format+in+2017.3 のページが表示されます。code style の設定が変わるようですが、一旦このままにしておきます。

    また上の画面キャプチャでは消えていますが、IntelliJ IDEA を再起動するダイアログも出ていたので再起動します。

  17. Plugin がアップデートされていないので先にアップデートします。IntelliJ IDEA のメインメニューから「Help」-「Check for Updates...」を選択します。

  18. IDE and Plugin Updates」ダイアログが表示されます。何も変更せずに「Update」ボタンをクリックします。

    f:id:ksby:20171216210430p:plain

    Patch がダウンロードされた後、IntelliJ IDEA を再起動します。再起動後、画面下部に「Indexing…」のメッセージが表示されますので、終了するまで待機します。

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

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

    f:id:ksby:20171216212021p:plain

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

    f:id:ksby:20171216212746p:plain

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

    f:id:ksby:20171216213239p:plain

  23. 2017.2 に戻るかもしれないので、C:\Users\root.IntelliJIdea2017.2 の削除は今回は保留します。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その40 )( Form.js のテストを Jest で書く )

概要

記事一覧はこちらです。

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

  • 今回の手順で確認できるのは以下の内容です。
    • Jest を使用して Javascript のモジュールのテストを書きます。
    • 今回は src/main/assets/js/lib/class/Form.js のテストを書きます。

参照したサイト・書籍

  1. How to duplicate object properties in another object?
    https://stackoverflow.com/questions/9362716/how-to-duplicate-object-properties-in-another-object

  2. Object.keys()
    https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Object/keys

目次

  1. テストを書くファイルを用意する
  2. focus イベントに関するメソッドのテストを書く
    1. setFocused メソッド
    2. isAllFocused メソッド
    3. setFocusedFromList メソッド
    4. forceAllFocused メソッド
    5. backupFocusedState, restoreFocusedState メソッド
  3. 値の入力有無をチェックするメソッドのテストを書く
    1. isAllEmpty メソッド
    2. isAnyEmpty メソッド
    3. isAnyNotEmpty メソッド
  4. 続きます。。。

手順

テストを書くファイルを用意する

src/test/assets/tests/lib の下に class ディレクトリを作成し、その下に Form.test.js を新規作成して以下の内容を記述します。

"use strict";

global.$ = require("jquery");
const Form = require("lib/class/Form.js");

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

    describe("focus イベントに関するメソッドのテスト", () => {

        beforeEach(() => {
            document.body.innerHTML = `
              <input type="text" name="name" id="name" value=""/>
              <input type="radio" name="age" id="age_1" value="1"/>男性
              <input type="radio" name="age" id="age_2" value="2"/>女性
              <input type="checkbox" name="enquete" id="enquete_1" value="1"/>回答1
              <input type="checkbox" name="enquete" id="enquete_2" value="2"/>回答2
              <select id="job">
                <option value="1">会社員</option>
                <option value="2">学生</option>
                <option value="3">その他</option>
              </select>
            `;
        });

        const idList = [
            "#name",
            "input:radio[name='age']",
            "input:checkbox[name='enquete']",
            "#job"
        ];

        // ここにテストを書く

    });

});

focus イベントに関するメソッドのテストを書く

setFocused メソッド

以下のテストを書いて、

        test("focusイベントが発生するとfocusedプロパティにselectorがセットされる", () => {
            let form = new Form(idList);
            expect(form.focused).not.toHaveProperty("#name", true);
            expect(form.focused).not.toHaveProperty("input:radio[name='age']", true);
            expect(form.focused).not.toHaveProperty("input:checkbox[name='enquete']", true);
            expect(form.focused).not.toHaveProperty("#job", true);

            $("#name").focus();
            expect(form.focused).toHaveProperty("#name", true);
            expect(form.focused).not.toHaveProperty("input:radio[name='age']", true);
            expect(form.focused).not.toHaveProperty("input:checkbox[name='enquete']", true);
            expect(form.focused).not.toHaveProperty("#job", true);

            $("input:radio[name='age']").focus();
            expect(form.focused).toHaveProperty("#name", true);
            expect(form.focused).toHaveProperty("input:radio[name='age']", true);
            expect(form.focused).not.toHaveProperty("input:checkbox[name='enquete']", true);
            expect(form.focused).not.toHaveProperty("#job", true);

            $("input:checkbox[name='enquete']").focus();
            expect(form.focused).toHaveProperty("#name", true);
            expect(form.focused).toHaveProperty("input:radio[name='age']", true);
            expect(form.focused).toHaveProperty("input:checkbox[name='enquete']", true);
            expect(form.focused).not.toHaveProperty("#job", true);

            $("#job").focus();
            expect(form.focused).toHaveProperty("#name", true);
            expect(form.focused).toHaveProperty("input:radio[name='age']", true);
            expect(form.focused).toHaveProperty("input:checkbox[name='enquete']", true);
            expect(form.focused).toHaveProperty("#job", true);
        });

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

f:id:ksby:20171213011149p:plain

isAllFocused メソッド

以下のテストを書いて、

        test("isAllFocused メソッドのテスト", () => {
            const idList2 = [
                "input:radio[name='age']",
                "input:checkbox[name='enquete']"
            ];
            let form = new Form(idList);
            expect(form.isAllFocused(form, idList)).toBe(false);

            $("#name").focus();
            expect(form.isAllFocused(form, idList)).toBe(false);

            $("input:radio[name='age']").focus();
            expect(form.isAllFocused(form, idList)).toBe(false);
            expect(form.isAllFocused(form, idList2)).toBe(false);

            $("input:checkbox[name='enquete']").focus();
            expect(form.isAllFocused(form, idList)).toBe(false);
            expect(form.isAllFocused(form, idList2)).toBe(true);

            $("#job").focus();
            expect(form.isAllFocused(form, idList)).toBe(true);
        });

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

f:id:ksby:20171213064213p:plain

setFocusedFromList メソッド

以下のテストを書いて、

        test("setFocusedFromList メソッドのテスト", () => {
            let form = new Form(idList);
            expect(form.isAllFocused(form, idList)).toBe(false);
            form.setFocusedFromList(form, idList);
            expect(form.isAllFocused(form, idList)).toBe(true);

            const idList2 = [
                "input:radio[name='age']",
                "input:checkbox[name='enquete']"
            ];
            let form2 = new Form(idList);
            expect(form2.isAllFocused(form2, idList)).toBe(false);
            form2.setFocusedFromList(form2, idList2);
            expect(form2.isAllFocused(form2, idList)).toBe(false);
            expect(form2.isAllFocused(form2, idList2)).toBe(true);
        });

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

f:id:ksby:20171213065154p:plain

forceAllFocused メソッド

以下のテストを書いて、

        test("forceAllFocused メソッドのテスト", () => {
            let form = new Form(idList);
            expect(form.isAllFocused(form, idList)).toBe(false);
            form.forceAllFocused(form);
            expect(form.isAllFocused(form, idList)).toBe(true);
        });

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

f:id:ksby:20171213065840p:plain

backupFocusedState, restoreFocusedState メソッド

以下のテストを書いて、

        test("backupFocusedState, restoreFocusedState メソッドのテスト", () => {
            let form = new Form(idList);
            form.forceAllFocused(form);
            expect(form.focused).toHaveProperty("#name", true);
            expect(form.focused).toHaveProperty("input:radio[name='age']", true);
            expect(form.focused).toHaveProperty("input:checkbox[name='enquete']", true);
            expect(form.focused).toHaveProperty("#job", true);
            expect(form.backupFocused).not.toHaveProperty("#name", true);
            expect(form.backupFocused).not.toHaveProperty("input:radio[name='age']", true);
            expect(form.backupFocused).not.toHaveProperty("input:checkbox[name='enquete']", true);
            expect(form.backupFocused).not.toHaveProperty("#job", true);

            form.backupFocusedState(form);
            expect(form.backupFocused).toHaveProperty("#name", true);
            expect(form.backupFocused).toHaveProperty("input:radio[name='age']", true);
            expect(form.backupFocused).toHaveProperty("input:checkbox[name='enquete']", true);
            expect(form.backupFocused).toHaveProperty("#job", true);

            form.focused = {};
            form.restoreFocusedState(form);
            expect(form.focused).toHaveProperty("#name", true);
            expect(form.focused).toHaveProperty("input:radio[name='age']", true);
            expect(form.focused).toHaveProperty("input:checkbox[name='enquete']", true);
            expect(form.focused).toHaveProperty("#job", true);
        });

実行するとテストが失敗しました。。。

f:id:ksby:20171213072333p:plain

いろいろ調べて How to duplicate object properties in another object?Object.keys() の記事を見つけました。オブジェクトのプロパティをコピーするのに配列のコピーと勘違いしていたからでした。。。 Form.js を以下のように変更します。

function Form(idList) {
    this.idList = idList;
    this.focused = {};
    this.backupFocused = {};
    addFocusEventListener(this);
}

..........

/**
 * form.focused の値をバックアップする
 * @param {Form} form - Form オブジェクト
 */
Form.prototype.backupFocusedState = function (form) {
    Object.keys(form.focused).forEach(function (key) {
        form.backupFocused[key] = form.focused[key];
    });
};

/**
 * form.focused の値を form.backupFocusedState を呼び出した時の状態に戻す
 * @param {Form} form - Form オブジェクト
 */
Form.prototype.restoreFocusedState = function (form) {
    Object.keys(form.backupFocused).forEach(function (key) {
        form.focused[key] = form.backupFocused[key];
    });
};
  • focused, backupFocused のプロパティの初期化を []{} に変更します。
  • backupFocusedState, restoreFocusedState メソッド内の処理を Object.keys() を使用する方法に変更します。

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

f:id:ksby:20171213231918p:plain

これまで作成したテストを全て実行しても成功しました。

f:id:ksby:20171213232032p:plain

値の入力有無をチェックするメソッドのテストを書く

isAllEmpty メソッド

以下のテストを書いて、

    describe("値の入力有無をチェックするメソッドのテストを書く", () => {

        beforeEach(() => {
            document.body.innerHTML = `
              <input type="text" name="name" id="name" value=""/>
              <input type="radio" name="age" id="age_1" value="1"/>男性
              <input type="radio" name="age" id="age_2" value="2"/>女性
              <input type="checkbox" name="enquete" id="enquete_1" value="1"/>回答1
              <input type="checkbox" name="enquete" id="enquete_2" value="2"/>回答2
              <select id="job">
                <option value="">選択してください</option>
                <option value="1">会社員</option>
                <option value="2">学生</option>
                <option value="3">その他</option>
              </select>
            `;
        });

        const idList = [
            "#name",
            "input:radio[name='age']",
            "input:checkbox[name='enquete']",
            "#job"
        ];

        test("isAllEmpty メソッドのテスト", () => {
            let form = new Form(idList);
            expect(form.isAllEmpty(idList)).toBe(true);

            // input[type='text']
            $("#name").val("a");
            expect(form.isAllEmpty(idList)).toBe(false);
            $("#name").val("");
            expect(form.isAllEmpty(idList)).toBe(true);

            // input[type='radio']
            $("input:radio[name='age'][value='1']").prop("checked", true);
            expect(form.isAllEmpty(idList)).toBe(false);
            $("input:radio[name='age'][value='1']").prop("checked", false);
            expect(form.isAllEmpty(idList)).toBe(true);
            $("input:radio[name='age'][value='2']").prop("checked", true);
            expect(form.isAllEmpty(idList)).toBe(false);
            $("input:radio[name='age'][value='2']").prop("checked", false);
            expect(form.isAllEmpty(idList)).toBe(true);

            // input[type='checkbox']
            $("input:checkbox[name='enquete'][value='1']").prop("checked", true);
            expect(form.isAllEmpty(idList)).toBe(false);
            $("input:checkbox[name='enquete'][value='1']").prop("checked", false);
            expect(form.isAllEmpty(idList)).toBe(true);
            $("input:checkbox[name='enquete'][value='2']").prop("checked", true);
            expect(form.isAllEmpty(idList)).toBe(false);
            $("input:checkbox[name='enquete'][value='2']").prop("checked", false);
            expect(form.isAllEmpty(idList)).toBe(true);
            $("input:checkbox[name='enquete'][value='1']").prop("checked", true);
            $("input:checkbox[name='enquete'][value='2']").prop("checked", true);
            expect(form.isAllEmpty(idList)).toBe(false);
            $("input:checkbox[name='enquete'][value='1']").prop("checked", false);
            $("input:checkbox[name='enquete'][value='2']").prop("checked", false);
            expect(form.isAllEmpty(idList)).toBe(true);

            // select
            $("#job").val("1");
            expect(form.isAllEmpty(idList)).toBe(false);
            $("#job").val("2");
            expect(form.isAllEmpty(idList)).toBe(false);
            $("#job").val("3");
            expect(form.isAllEmpty(idList)).toBe(false);
            $("#job").val("");
            expect(form.isAllEmpty(idList)).toBe(true);
        });

    });

実行するとテストが失敗しました。最初の expect(form.isAllEmpty(idList)).toBe(true); で失敗していますが、checkbox の処理を実装していないので、当然ですね。。。

f:id:ksby:20171215003536p:plain

Form.js を以下のように変更します。

/**
 * idList で渡された id の要素が全て未入力/未選択かチェックする
 * @param {Array} idList - チェックする id がセットされた配列
 * @returns {boolean} true:全て未入力/未選択である, false:1つ以上入力/選択されているものがある
 */
Form.prototype.isAllEmpty = function (idList) {
    var allEmpty = true;
    idList.forEach(function (id) {
        if ($(id).attr("type") === "radio") {
            if ($(id + ":checked").val() !== undefined) {
                allEmpty = false;
            }
        } else if ($(id).attr("type") === "checkbox") {
            if ($(id + ":checked").length > 0) {
                allEmpty = false;
            }
        } else if ($(id).val() !== "") {
            allEmpty = false;
        }
    });
    return allEmpty;
};
  • else if ($(id).attr("type") === "checkbox") { ... } の処理を追加します。

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

f:id:ksby:20171215011024p:plain

isAnyEmpty メソッド

isAnyEmpty メソッドも checkbox の実装をしていないので、テストの前に修正します。Form.js を以下のように変更します。

/**
 * idList で渡された id の要素の中に未入力/未選択のものがあるかチェックする
 * @param {Array} idList - チェックする id がセットされた配列
 * @returns {boolean} true:未入力/未選択のものがある, false:全て入力/選択されている
 */
Form.prototype.isAnyEmpty = function (idList) {
    var anyEmpty = false;
    idList.forEach(function (id) {
        if ($(id).attr("type") === "radio") {
            if ($(id + ":checked").val() === undefined) {
                anyEmpty = true;
            }
        } else if ($(id).attr("type") === "checkbox") {
            if ($(id + ":checked").length === 0) {
                anyEmpty = true;
            }
        } else if ($(id).val() === "") {
            anyEmpty = true;
        }
    });
    return anyEmpty;
};
  • else if ($(id).attr("type") === "checkbox") { ... } を追加します。

以下のテストを書いて、

        test("isAnyEmpty メソッドのテスト", () => {
            let form = new Form(idList);
            expect(form.isAnyEmpty(idList)).toBe(true);

            $("#name").val("a");
            $("input:radio[name='age'][value='1']").prop("checked", true);
            $("input:checkbox[name='enquete'][value='1']").prop("checked", true);
            $("#job").val("1");
            expect(form.isAnyEmpty(idList)).toBe(false);

            $("#name").val("");
            expect(form.isAnyEmpty(idList)).toBe(true);
            $("#name").val("a");
            expect(form.isAnyEmpty(idList)).toBe(false);

            $("input:radio[name='age'][value='1']").prop("checked", false);
            expect(form.isAnyEmpty(idList)).toBe(true);
            $("input:radio[name='age'][value='1']").prop("checked", true);
            expect(form.isAnyEmpty(idList)).toBe(false);

            $("input:checkbox[name='enquete'][value='1']").prop("checked", false);
            expect(form.isAnyEmpty(idList)).toBe(true);
            $("input:checkbox[name='enquete'][value='1']").prop("checked", true);
            expect(form.isAnyEmpty(idList)).toBe(false);
            $("input:checkbox[name='enquete'][value='2']").prop("checked", true);
            expect(form.isAnyEmpty(idList)).toBe(false);
            $("input:checkbox[name='enquete'][value='1']").prop("checked", false);
            expect(form.isAnyEmpty(idList)).toBe(false);
            $("input:checkbox[name='enquete'][value='2']").prop("checked", false);
            expect(form.isAnyEmpty(idList)).toBe(true);
            $("input:checkbox[name='enquete'][value='1']").prop("checked", true);

            $("#job").val("");
            expect(form.isAnyEmpty(idList)).toBe(true);
            $("#job").val("1");
            expect(form.isAnyEmpty(idList)).toBe(false);
        });

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

f:id:ksby:20171216071456p:plain

isAnyNotEmpty メソッド

isAnyNotEmpty メソッドも checkbox の実装をしていないので、テストの前に修正します。Form.js を以下のように変更します。

/**
 * idList で渡された id の要素の中に1つでも入力/選択されているものがあるかチェックする
 * @param {Array} idList - チェックする id がセットされた配列
 * @returns {boolean} true:1つでも入力/選択されている, false:全て未入力/未選択である
 */
Form.prototype.isAnyNotEmpty = function (idList) {
    var anyNotEmpty = false;
    idList.forEach(function (id) {
        if ($(id).attr("type") === "radio") {
            if ($(id + ":checked").val() !== undefined) {
                console.log(id + ", " + $(id + ":checked").val());
                anyNotEmpty = true;
            }
        } else if ($(id).attr("type") === "checkbox") {
            if ($(id + ":checked").length > 0) {
                console.log(id + ", " + $(id + ":checked").length);
                anyNotEmpty = true;
            }
        } else if ($(id).val() !== "") {
            console.log(id + ", " + $(id).val());
            anyNotEmpty = true;
        }
    });
    return anyNotEmpty;
};
  • else if ($(id).attr("type") === "radio") { ... } を追加します。

以下のテストを書いて、

        test("isAnyNotEmpty メソッドのテスト", () => {
            let form = new Form(idList);
            expect(form.isAnyNotEmpty(idList)).toBe(false);

            // input[type='text']
            $("#name").val("a");
            expect(form.isAnyNotEmpty(idList)).toBe(true);
            $("#name").val("");
            expect(form.isAnyNotEmpty(idList)).toBe(false);

            $("input:radio[name='age'][value='1']").prop("checked", true);
            expect(form.isAnyNotEmpty(idList)).toBe(true);
            $("input:radio[name='age'][value='1']").prop("checked", false);
            expect(form.isAnyNotEmpty(idList)).toBe(false);

            $("input:checkbox[name='enquete'][value='1']").prop("checked", true);
            expect(form.isAnyNotEmpty(idList)).toBe(true);
            $("input:checkbox[name='enquete'][value='1']").prop("checked", false);
            expect(form.isAnyNotEmpty(idList)).toBe(false);
            $("input:checkbox[name='enquete'][value='2']").prop("checked", true);
            expect(form.isAnyNotEmpty(idList)).toBe(true);
            $("input:checkbox[name='enquete'][value='2']").prop("checked", false);
            expect(form.isAnyNotEmpty(idList)).toBe(false);
            $("input:checkbox[name='enquete'][value='1']").prop("checked", true);
            $("input:checkbox[name='enquete'][value='2']").prop("checked", true);
            expect(form.isAnyNotEmpty(idList)).toBe(true);
            $("input:checkbox[name='enquete'][value='1']").prop("checked", false);
            $("input:checkbox[name='enquete'][value='2']").prop("checked", false);
            expect(form.isAnyNotEmpty(idList)).toBe(false);

            $("#job").val("1");
            expect(form.isAnyNotEmpty(idList)).toBe(true);
            $("#job").val("");
            expect(form.isAnyNotEmpty(idList)).toBe(false);
        });

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

f:id:ksby:20171216184029p:plain

ここまで作成したテストが全て成功することを確認します。

f:id:ksby:20171216185101p:plain

続きます。。。

Jest + IntelliJ IDEA を組み合わると jQuery を利用した Javascript のモジュールのテストが書きやすいです。document.body.innerHTML に必要最低限の HTML だけ記述して動作確認が出来るのが便利ですね。

IntelliJ IDEA を 2017.3.1 へバージョンアップしてから、Form.js のテストを続けます。

履歴

2017/12/16
初版発行。

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 で作業を進めたいと思います。