テストプログラムを作成する(チュートリアル)

最終更新日: 2020年6月12日
R8 | R9

サンプルのダウンロード

ここではサンプルの「顧客」モデルに対するテストプログラムの書き方を説明します。はじめに顧客モデルのリポジトリをダウンロードし、Wagby でビルドしてください。

図1 サンプルの顧客モデル

テストクラスを試す

ビルドが完了したら Eclipse を起動し、この環境(Wagby一式)を Eclipse プロジェクトとして扱えるようにしてください。chromedriver.exe は customize フォルダ直下にあるとします。詳細は「Wagby Testing Framework とは > 準備」をお読みください。

開発機のメモリが少ない場合、WagbyDesignerを停止してから Eclipse を起動するとよいでしょう。

ソース・フォルダーを "(Wagbyインストールフォルダ)/customize/test/java" とし、パッケージを "jp.jasminesoft.wagby.tests.t001_Customer" とします。ここにテストクラス "CustomerTest" を用意します。

なお今回は、すでに用意したテストクラスをそのまま使います。

ダウンロードしたファイルを展開します。得られた CustomerTest.java を同パッケージが示すフォルダ (customize/test/java/jp/jasminesoft/wagby/tests/t001_Customer) にコピーしたあと、Eclipse の環境をリフレッシュすることで、追加ファイルを認識させることができます。

図2 Eclipseにテストクラスを登録する

「サーバー」タブで Tomcat を起動します。

起動後、CustomerTest を実行します。「メニューバー > 実行 > 実行 > JUnit テスト」です。

図3 自動テスト途中の画面

すべてのテストが成功すると図4のような画面になります。

図4 JUnitテストの終了

テストクラスの解説

CustomerTestクラスの説明を通して、テストコードの書き方を学びます。

前処理

WTF は jp.jasminesoft.jfc.test.support.selenide パッケージに、Dojotoolkit の部品を操作するためのクラスを提供しています。例えば ComboBox, DatePicker, ListBox というものです。これをインポートしています。

setUpBeforeClass メソッドで、利用する WebDriver を指定します。ただし WTF では chromedriver の利用を前提としています。

tearDownAfterClass メソッドは空としています。今回は後処理は何もありません。

package jp.jasminesoft.wagby.tests.t001_Customer;
import static com.codeborne.selenide.CollectionCondition.*;
import static com.codeborne.selenide.Condition.*;
import static com.codeborne.selenide.Selenide.*;
import static jp.jasminesoft.jfc.test.support.selenide.Operations.*;
import static jp.jasminesoft.util.ExcelFunction.*;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import java.util.Date;
import com.codeborne.selenide.Configuration;
import com.codeborne.selenide.WebDriverRunner;
import jp.jasminesoft.jfc.test.support.selenide.ComboBox;
import jp.jasminesoft.jfc.test.support.selenide.DatePicker;
import jp.jasminesoft.jfc.test.support.selenide.DateTextBox;
import jp.jasminesoft.jfc.test.support.selenide.ListBox;
import jp.jasminesoft.jfc.test.support.selenide.RadioButton;
import jp.jasminesoft.jfc.test.support.selenide.WebContainerModelitem;
import jp.jasminesoft.jfc.test.support.selenide.WebModel;
import jp.jasminesoft.jfc.test.support.selenide.WebModelitem;
import jp.jasminesoft.jfc.test.support.selenide.WebMultiModelitem;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runners.MethodSorters;
/**
 * 1. Customer モデルのテスト
 *
 * @author JasmineSoft
 * @version $Revision$ $Date$
 */
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class CustomerTest {
    /**
     * テストの前処理を行います。
     */
    @BeforeClass
    public static void setUpBeforeClass() throws Exception {
        // アクセスする URL のベース
        Configuration.baseUrl = "http://localhost:8921/wagby";
        // 利用するブラウザ
        Configuration.browser = WebDriverRunner.CHROME;
        // Selenium(WebDriver) 用ドライバファイル
        System.setProperty("webdriver.chrome.driver",
                "customize/chromedriver.exe");
    }
    /**
     * 各テスト実行後の後処理を行います。
     */
    @AfterClass
    public static void tearDownAfterClass() throws Exception {
    }

test01ログオンのテスト

ここからテストメソッドを解説します。すべてのテストメソッドに、アノテーション @Test を含めます。また今回は testXX という接頭語を付与しています。メソッド名に具体的なテスト処理を明記することで、何のテストかが把握しやすくなります。

WTF では、ログオン処理は Operations.logon メソッドで実現します。staticインポートにより、クラス名 (Operations) を省略することができます。

ログオン後はメニュー画面が表示されることを確認するために、pageTitle().shouldHave メソッドを使います。exactText メソッドは、文字列が一致していることを判断するものです。

    /**
     * ログオン処理のテスト
     */
    @Test
    public void test01ログオンのテスト() {
        // ログオン処理を行う。
        //Operations.logon("admin", "wagby");
        logon("admin", "wagby");
        // ページタイトルを確認
        pageTitle().shouldHave(exactText("メニュー"));
    }

test02ログオフのテスト

WTF では、ログオフ処理は logoff メソッドの呼び出しで実現します。ログオフが正しく行われたかのテストは、その後の HTML ページのタイトルで判定しています。

    /**
     * ログオフ処理のテスト
     */
    @Test
    public void test02ログオフのテスト() {
        // ログオフ処理を行う。
        logoff();
        // checkNoErrors(); // 不要。
        // HTMLのタイトルを確認
        assertThat(title(), is("Wagby アプリケーション ログオン"));
    }

test04メニューのテスト

数字が一つ飛んでいますが、気にしないでください。特に連番である必要はありません。

test02 でいったんログオフしていますので、再度ログオンします。この処理は test01 と同じものですが重複しても同じテストコードを書くようにしてください。これはテストコードは一読することで何が行われているかを把握することができるようにする、というポリシーです。

WTF ではメニューからの選択処理は selectMenu メソッドを使って行います。第一引数がメニュー大項目で、第二引数がメニュー名です。処理の途中でメニューに戻るためには menu メソッドを使います。

サブメニューを使うこともできます。この場合は selectSubMenu メソッドを使います。

    /**
     * メニューのテスト
     */
    @Test
    public void test04メニューのテスト() {
        // ログオン処理を行う。
        logon("admin", "wagby");
        // ページタイトルを確認
        pageTitle().shouldHave(exactText("メニュー"));
        // サブメニューでの画面遷移
        // 大項目「サービス」を選択後、プルダウンから「顧客検索」を選択する
        selectMenu("サービス", "顧客情報検索");
        pageTitle().shouldHave(exactText("顧客情報 検索"));
        // メニュー画面に戻る。
        menu();
        pageTitle().shouldHave(exactText("メニュー"));
        // 大項目「サービス」を選択後、プルダウンから「顧客情報検索」を選択する
        selectSubMenu("サービス", "顧客情報検索");
        pageTitle().shouldHave(exactText("顧客情報 検索"));
    }

画面遷移を行なった場合のテストコードの記述方法

テストコード中に画面遷移を伴う操作を行った場合は、直後にページタイトルを確認する処理を行うようにして下さい。 上記コードサンプルでは、次の部分になります。

   // メニュー画面を表示する。
   menu();
   // ページタイトルを確認する。
   pageTitle().shouldHave(exactText("メニュー"));

これは、予定していた画面へ遷移したことを確認するとともに、遷移後のページが正しく表示されるまで以降のテストを待機させる意味を持ちます。(デフォルトの待機時間は4秒となっています。)

ページタイトルの確認処理を行わない場合は、ページの内容が全て表示される前に次のテストが実行されてしまい、意図せぬテスト失敗を引き起こしてしまうことがあります。(*)

WTFのベースとなっている Selenide には、テスト対象のHTML要素が切り替わるまで自動的にwaitを行う機能があります。しかし Wagby が利用するJavaScriptフレームワークDojo Toolkitとの相性により、同機能がうまく働かない場合があります。そのため、安定したテストを実現するために「画面遷移後にページタイトルの確認処理を行う」ことは非常に効果的です。

ページ描画が早いテスト環境ではテストが成功し、描画が遅いテスト環境ではテストが失敗してしまう、等。

test05登録画面への遷移テスト

メニューから顧客モデルの新規登録画面へ遷移するテストです。メニューから新規登録画面へ遷移させる場合は clinkNewButton メソッドを使います。引数に対象モデル名を指定しますが、今回は登録画面へ遷移するボタンが一つしかありませんので、引数は省略可能です。

    /**
     * 登録画面への遷移のテスト
     */
    @Test
    public void test05登録画面への遷移テスト() {
        // 「登録画面へ」ボタンをクリック
        //clickNewButton("customer");
        // 登録画面用ボタンが一つの場合は"customer"は省略可
        clickNewButton();
        //clickEditButton(); //更新用(ルールは「登録画面へ」のボタンと同じ)
        pageTitle().shouldHave(exactText("顧客情報 新規登録"));
    }

test06保存処理のテスト

項目に値を入力し、保存ボタンを押したあと、詳細画面に遷移することを確認してみます。

WTF では、入力欄は WebModelitem オブジェクトで表現します。第一引数は対象モデルを指定しますが、これは WebModel オブジェクトで表現されるものです。第二引数に項目名を指定します。テキストフィールドへの入力操作は WebModelitem オブジェクトが提供する val メソッドを使うことができます。

保存処理は save メソッドの呼び出しで行います。このメソッドを呼び出すと画面が遷移しますので、遷移後の画面のページタイトルを判定することで、処理が正しく行われたかどうかを確認することができます。

    /**
     * 保存処理のテスト
     */
    @Test
    public void test06保存処理のテスト() {
        // 保存させるために必須項目に値を入力。
        new WebModelitem<>(new WebModel("customer"), "name").val("a");
        // 「保存」ボタンをクリック
        save();
        // ページタイトルを確認
        pageTitle().shouldHave(exactText("顧客情報 詳細表示"));
    }

test07キャンセル処理のテスト

登録画面に遷移後、キャンセルボタンを押下ときに正しく詳細画面に戻っているかどうかのテストです。

WTF では、cancel メソッドの呼び出しで、キャンセルボタン押下操作を行います。このメソッドはさらに、確認用ダイアログで「OK」ボタンを押す処理まで行います。

    /**
     * キャンセル処理のテスト
     */
    @Test
    public void test07キャンセル処理のテスト() {
        // まず、登録画面へ遷移する。
        clickNewButton();
        pageTitle().shouldHave(exactText("顧客情報 新規登録"));
        // 「キャンセル」ボタンをクリック
        // 「キャンセル」ボタンクリック後に表示される確認ダイアログも
        // 自動的にOKをクリックします。
        cancel();
        // ページタイトルを確認
        pageTitle().shouldHave(exactText("顧客情報 詳細表示"));
    }

test08登録画面での各項目への入力テスト

ここまでで登録画面への遷移と保存およびキャンセルボタン押下時の動作までテストすることができました。続いて、登録画面への値入力テストへ発展させていきます。

WebModel

すべての入力欄は第一引数に WebModel オブジェクトが必要です。これは一つ用意すれば共通で利用できます。

ラジオボタン

ラジオボタンは RadioButton オブジェクトを使います。

繰り返し項目

繰り返し項目は WebMultiModelitem を使います。メソッド呼び出しはチェーン方式(数珠つなぎ)に対応しています。get, val, add メソッドを連続して呼び出すことで、画面上の操作を実現します。さらに、val メソッドに複数の引数を渡すことで、内部で get, val, add を繰り返して行います。

繰り返しコンテナ

繰り返しコンテナは WebContainerModelitem を使います。続く WebModelitem の作成では、第一引数は WebModel ではなく、WebContainerModel を渡すことで、同じようなコードを書くことができるようになっています。

日付

カレンダ補助入力が利用できる日付項目は、DatePicker オブジェクトを使います。nextMonth, prevMonth, nextYear, prevYear メソッドでカレンダの操作を行います。selectDate メソッドで、表示されている月の日にちを指定できます。

追記型リストボックス

WTF では追記型リストボックスを ComboBox オブジェクトで表現します。コードではコメントになっていますが、dropdown メソッドを使うことで選択肢から選ぶこともできます。

読み込み専用項目

指定した項目が「読み込み専用」であるかどうかの確認は、shouldBeReadOnly メソッドを使って判定できます。さらに shouldHave メソッドを使うことで、値の判定も行うことができます。

Wagbyの関数を利用する

テストクラスでは import static jp.jasminesoft.util.ExcelFunction.*; を宣言しているため、Wagbyの標準関数 TEXT や NOW を利用することができるようにしています。

    /**
     * 登録画面での各項目への入力テスト
     */
    @Test
    public void test08登録画面での各項目への入力テスト() {
        // 登録画面へ遷移する。
        clickNewButton();
        pageTitle().shouldHave(exactText("顧客情報 新規登録"));
        // モデルの情報を保持するインスタンス
        WebModel model = new WebModel("customer");
        // customerモデルのname項目への入力
        new WebModelitem<>(model, "name").val("ジャスミン太郎");
        // 「ジャスミン太郎」と入力されていることを確認
        new WebModelitem<>(model, "name").shouldHave("ジャスミン太郎");
        // ラジオボタン:「地方公共団体」を選択する
        new RadioButton(model, "customertype").val("地方公共団体");
        // 繰返し項目
        // 繰り返し項目「email」を操作するオブジェクトを作成。
        new WebMultiModelitem<>(model, "email")
                //.clear() // 既存の入力値全てを削除
                .get(1) // 1番目の入力フィールドを取得
                .val("taro@jasminesoft.co.jp")
                .add()  // 「追加」ボタンをクリック
                .val("sales@jasminesoft.co.jp");
        // こちらでも可(内部処理は同じ)
        new WebMultiModelitem<>(model, "email")
                .val("taro@jasminesoft.co.jp", "sales@jasminesoft.co.jp");
        // 繰り返しコンテナを操作するオブジェクトを作成。
        WebContainerModelitem<?> report
                = new WebContainerModelitem<>(model, "report");
        // コンテナの1行目があればこれを取得する(なければエラー)。
        WebContainerModelitem<?> report01 = report.get(1);
        // コンテナ1行目の「rnote」項目への入力
        new WebModelitem<>(report01, "rnote").val("Wagby 購入");
        DatePicker<?> datePicker
                = new DateTextBox(report01, "rdate").datePicker();
        datePicker.nextMonth();   // 翌月へ
        datePicker.prevMonth();   // 前月へ
        datePicker.nextYear();    // 翌年へ
        datePicker.prevYear();    // 前年へ
        datePicker.selectDate(1); // 1日を選択
        // コンテナの2行目がない状態で実行してもエラーとなる。
        //WebContainerModelitem report02 = report.get(2);
        // コンテナの「追加」ボタンをクリックして、新規行を取得。
        WebContainerModelitem<?> report02 = report.add();
        datePicker = new DateTextBox(report02, "rdate").datePicker();
        datePicker.nextMonth(); // 翌月へ
        datePicker.prevMonth(); // 前月へ
        datePicker.nextYear(); // 翌年へ
        datePicker.prevYear(); // 前年へ
        datePicker.selectDate(2); // 1日を選択
        // コンテナ2行目の「rnote」項目への入力
        new WebModelitem<>(report02, "rnote").val("Wagby 保守契約更新");
        // 追記型リストボックス
        ComboBox companyname = new ComboBox(model, "companyname");
        // 直接入力を行う場合(存在しない選択肢も可)
        companyname.val("ジャスミンソフト");
        // ドロップダウンから選択する場合
        // 存在しない選択肢を選んだ場合はエラー
        //companyname
        //    .dropdown()
        //    .select("ジャスミンソフト");
        // 読込専用項目の確認:最終更新者
        new WebModelitem<>(model, "updateuserid")
                .shouldBeReadOnly()
                .shouldHave("admin");
        // 読込専用項目の確認:データ作成日
        new WebModelitem<>(model, "insertrec")
                .shouldBeReadOnly()
                .shouldHave(text(TEXT(NOW(), "yyyy-MM-dd HH:mm:ss")));
        // 読込専用項目の確認:データ更新日
        new WebModelitem<>(model, "updaterec")
                .shouldBeReadOnly()
                .shouldHave(text(TEXT(NOW(), "yyyy-MM-dd HH:mm:ss")));
        // 「保存」ボタンをクリック
        save();
        // ページタイトルを確認
        pageTitle().shouldHave(exactText("顧客情報 詳細表示"));
    }

test09更新画面での各項目への入力テスト

保存後、今度は更新画面へ遷移し、値を変更するテストです。更新画面への遷移は clickEditButton メソッドを利用することができます。

リストボックス

リストボックスは ListBox オブジェクトを利用します。繰り返しコンテナ内のリストボックスを操作することもできます。

    /**
     * 更新画面での各項目への入力テスト
     */
    @Test
    public void test09更新画面での各項目への入力テスト() {
        // 更新画面へ遷移する。
        clickEditButton();
        pageTitle().shouldHave(exactText("顧客情報 更新"));
        // モデルの情報を保持するインスタンス
        WebModel model = new WebModel("customer");
        // customerモデルのkananame項目への入力
        new WebModelitem<>(model, "kananame").val("ジャスミンタロウ");
        new WebModelitem<>(model, "companykananame").val("ジャスミンソフト");
        new WebModelitem<>(model, "deptname").val("技術開発部");
        new WebModelitem<>(model, "title").val("一般");
        new WebModelitem<>(model, "zipcode").val("9012227");
        new WebModelitem<>(model, "address").val("沖縄県宜野湾市宇地泊902-1");
        new WebModelitem<>(model, "tel").val("098-890-6036");
        new WebModelitem<>(model, "fax").val("098-890-6038");
        // 繰り返しコンテナを操作するオブジェクトを作成。
        WebContainerModelitem<?> report
                = new WebContainerModelitem<>(model, "report");
        new ListBox(report.get(1), "rstatus").val("A");
        new ListBox(report.get(2), "rstatus").val("A");
        // 「保存」ボタンをクリック
        save();
        // ページタイトルを確認
        pageTitle().shouldHave(exactText("顧客情報 詳細表示"));
    }

test10詳細画面での各項目の入力値の確認テスト

詳細画面で、値が正しいかどうかを確認するためのテストです。基本は WebModelitem オブジェクトが提供する shouldHave メソッドを使います。

    /**
     * 詳細画面での各項目の値の確認テスト
     */
    @Test
    public void test10詳細画面での各項目の入力値の確認テスト() {
        // ページタイトルを確認
        pageTitle().shouldHave(exactText("顧客情報 詳細表示"));
        // モデルの情報を保持するインスタンス
        WebModel model = new WebModel("customer");
        // customerモデルのname項目の値の確認
        new WebModelitem<>(model, "name").shouldHave("ジャスミン太郎");
        // ラジオボタン
        new RadioButton(model, "customertype").shouldHave("地方公共団体");
        // 繰返し項目
        new WebMultiModelitem<>(model, "email").shouldHave(
                "taro@jasminesoft.co.jp", "sales@jasminesoft.co.jp");
        // 繰り返しコンテナを操作するオブジェクトを作成。
        WebContainerModelitem<?> report
                = new WebContainerModelitem<>(model, "report");
        // コンテナの1行目を取得。
        WebContainerModelitem<?> report01 = report.get(1);
        Date bomonth = BOMONTH(NOW());
        String dateText = TEXT(bomonth, "yyyy-MM-dd");
        // コンテナ1行目の「rnote」項目への入力
        new WebModelitem<>(report01, "rnote").shouldHave("Wagby 購入");
        new DateTextBox(report01, "rdate").shouldHave(dateText);
        // コンテナの2行目を取得。
        WebContainerModelitem<?> report02 = report.get(2);
        dateText = TEXT(MOVEDAY(bomonth, 1), "yyyy-MM-dd");
        new DateTextBox(report02, "rdate").shouldHave(dateText);
        new WebModelitem<>(report02, "rdate").shouldHave(dateText);
        // コンテナ2行目の「rnote」項目の値の確認
        new WebModelitem<>(report02, "rnote").shouldHave("Wagby 保守契約更新");
        // 追記型リストボックス
        new ComboBox(model, "companyname").shouldHave("ジャスミンソフト");
        // 読込専用項目の確認:最終更新者
        new WebModelitem<>(model, "updateuserid")
                .shouldHave("admin");
        // 読込専用項目の確認:データ作成日
        new WebModelitem<>(model, "insertrec")
                .shouldHave(text(TEXT(NOW(), "yyyy-MM-dd HH:mm:ss")));
        // 読込専用項目の確認:データ更新日
        new WebModelitem<>(model, "updaterec")
                .shouldHave(text(TEXT(NOW(), "yyyy-MM-dd HH:mm:ss")));
    }

test11詳細画面での各項目の入力値の確認テスト2

test10に含まれていない項目を扱っています。

    /**
     * 詳細画面での各項目の値の確認テスト2
     */
    @Test
    public void test11詳細画面での各項目の入力値の確認テスト2() {
        // ページタイトルを確認
        pageTitle().shouldHave(exactText("顧客情報 詳細表示"));
        // モデルの情報を保持するインスタンス
        WebModel model = new WebModel("customer");
        // customerモデルのkananame項目への入力
        new WebModelitem<>(model, "kananame").shouldHave("ジャスミンタロウ");
        new WebModelitem<>(model, "companykananame").shouldHave("ジャスミンソフト");
        new WebModelitem<>(model, "deptname").shouldHave("技術開発部");
        new WebModelitem<>(model, "title").shouldHave("一般");
        new WebModelitem<>(model, "zipcode").shouldHave("9012227");
        new WebModelitem<>(model, "address").shouldHave("沖縄県宜野湾市宇地泊902-1");
        new WebModelitem<>(model, "tel").shouldHave("098-890-6036");
        new WebModelitem<>(model, "fax").shouldHave("098-890-6038");
        // 繰り返しコンテナを操作するオブジェクトを作成。
        WebContainerModelitem<?> report
                = new WebContainerModelitem<>(model, "report");
        new ListBox(report.get(1), "rstatus").shouldHave("A");
        new ListBox(report.get(2), "rstatus").shouldHave("A");
    }

test99ログオフ

最後にログオフを行います。他のメソッドがあとから追加されてもよいように、最後に呼び出されるメソッドは test99 など、これで終わりという意味を込めた接頭語を付与するとよいです。

    /**
     * ログオフ
     */
    @Test
    public void test99ログオフ() {
        logoff();
    }

再テストの場合

今回のテストクラスは再テスト可能な内容になっていますが、例えばデータが初期状態になることを前提としているテストの場合、次の手順を行ってから再テストしてください。

内蔵データベース利用時

  1. EclipseからTomcatを停止します。
  2. wagbyapp/bin/drop_db.bat を実行し、データベースを削除します。
  3. wagbyapp/bin/init_db.bat を実行し、データベースを再作成します。
  4. Eclipse の Wagby 環境をリフレッシュします。
  5. Eclipse から Tomcat を再起動します。

外部データベース利用時

Eclipse 上の Tomcat はそのままに、drop_db と init_db で外部データベースを再構築してください。

WTFモデルクラスを作成する

テストの実装方法の一つであるPageObjectデザインパターンを説明します。

アプリケーションの画面を1つのオブジェクトとして作成し、画面内のボタンや入力項目をPageObjectに保持させます。 それとは別にテストシナリオを記述するクラスを用意し、同クラスからPageObjectを操作する方式です。

この方式にすることでアプリケーション側の修正によるテストコードへの影響は、ほぼPageObjectのみとなり、PageObjectを変更するだけで、(シナリオ用のクラスを修正することなく)、変更後のアプリケーションでテストを動作させることができるようになります。

ただし、Wagby アプリケーションでは設計情報に従い、規則正しい画面を多く生成するため、WTFにこの概念をそのまま適用すると、登録画面、更新画面、コピー登録画面等でほぼ同じPageObjectを複数作成することになってしまいます。 これを避けるために、PageObjectよりもう少し適用範囲を広く扱うことのできるモデルベースのObjectを作成し(これをWTFモデルクラスと呼ぶことにします)、同クラスに各画面の項目情報を保持させることで更に保守性を向上させるようにします。

WTFモデルクラス Customer

先に紹介したCustomerTestクラスでは多くの項目情報がそのまま同クラス内に記述されていますが、これを Customer クラスへ分離し、CustomerTestはCustomerクラスを操作する形に書き換えてみます。これによって登録画面だけでなく、更新画面やコピー登録画面でもCustomerクラスを利用しやすくなります。

ここで紹介する Customer クラスは、WTF が提供する WebModel クラスを継承し、リポジトリで定義した項目を WebModelitem として用意しています。また、繰り返しコンテナは WebContainerModelitem クラスを継承した Report を内部クラスとして定義しています。

package jp.jasminesoft.wagby.tests.models.customer;
import jp.jasminesoft.jfc.test.support.selenide.ComboBox;
import jp.jasminesoft.jfc.test.support.selenide.DateTextBox;
import jp.jasminesoft.jfc.test.support.selenide.ListBox;
import jp.jasminesoft.jfc.test.support.selenide.Postcode;
import jp.jasminesoft.jfc.test.support.selenide.RadioButton;
import jp.jasminesoft.jfc.test.support.selenide.WebContainerModelitem;
import jp.jasminesoft.jfc.test.support.selenide.WebModel;
import jp.jasminesoft.jfc.test.support.selenide.WebModelitem;
import jp.jasminesoft.jfc.test.support.selenide.WebMultiModelitem;
/**
 * Customer モデルの情報を保持するクラス。
 *
 * @author JasmineSoft
 * @version $Revision$ $Date$
 */
public class Customer extends WebModel {
    /**
     * コンストラクタ。
     */
    public Customer() {
        super("customer");
    }
    /** customer/customerid */
    public final WebModelitem<?> customerid
            = new WebModelitem<>(this, "customerid");
    /** customer/name */
    public final WebModelitem<?> name
            = new WebModelitem<>(this, "name");
    /** customer/kananame */
    public final WebModelitem<?> kananame
            = new WebModelitem<>(this, "kananame");
    /** customer/companyname */
    public final ComboBox companyname
            = new ComboBox(this, "companyname");
    /** customer/companykananame */
    public final WebModelitem<?> companykananame
            = new WebModelitem<>(this, "companykananame");
    /** customer/deptname */
    public final WebModelitem<?> deptname
            = new WebModelitem<>(this, "deptname");
    /** customer/title */
    public final WebModelitem<?> title
            = new WebModelitem<>(this, "title");
    /** customer/zipcode */
    public final Postcode zipcode
            = new Postcode(this, "zipcode");
    /** customer/address */
    public final WebModelitem<?> address
            = new WebModelitem<>(this, "address");
    /** customer/url */
    public final WebModelitem<?> url
            = new WebModelitem<>(this, "url");
    /** customer/email */
    public final WebMultiModelitem<?> email
            = new WebMultiModelitem<>(this, "email");
    /** customer/tel */
    public final WebModelitem<?> tel
            = new WebModelitem<>(this, "tel");
    /** customer/fax */
    public final WebModelitem<?> fax
            = new WebModelitem<>(this, "fax");
    /** customer/customertype */
    public final RadioButton customertype
            = new RadioButton(this, "customertype");
    /** customer/report */
    public final Report report = new Report(this);
    /**
     * customer の繰り返しコンテナ report 内の各入力項目を保持するクラス。
     */
    public class Report extends WebContainerModelitem<Report> {
        /**
         * コンストラクタ。
         * @param model モデル情報
         */
        public Report(WebModel model) {
            super(model, "report");
        }
        /**
         * コンストラクタ。
         * @param model モデル情報
         * @param index コンテナ番号
         */
        public Report(WebModel model, int index) {
            super(model, "report", index);
        }
        /** {@inheritDoc} */
        @Override
        protected Report newInstance(int idx) {
            return new Report(model, idx);
        }
        /** customer/report/rid */
        public final WebModelitem<?> rid = new WebModelitem<>(this, "rid");
        /** customer/report/rdate */
        public final DateTextBox rdate = new DateTextBox(this, "rdate");
        /** customer/report/rnote */
        public final WebModelitem<?> rnote = new WebModelitem<>(this, "rnote");
        /** customer/report/rstatus */
        public final ListBox rstatus = new ListBox(this, "rstatus");
    }
    /** customer/memo */
    public final WebModelitem<?> memo
            = new WebModelitem<>(this, "memo");
    /** customer/updateuserid */
    public final WebModelitem<?> updateuserid
            = new WebModelitem<>(this, "updateuserid");
    /** customer/insertrec */
    public final WebModelitem<?> insertrec
            = new WebModelitem<>(this, "insertrec");
    /** customer/updaterec */
    public final WebModelitem<?> updaterec
            = new WebModelitem<>(this, "updaterec");
}

CustomerTestクラス

CustomerTest クラスは、上で定義した Customer クラスのインスタンス customer を使うように書き換えています。これによってモデルを操作する処理がより簡潔に記述されることがわかります。

package jp.jasminesoft.wagby.tests.t001_Customer;
import static com.codeborne.selenide.CollectionCondition.*;
import static com.codeborne.selenide.Condition.*;
import static com.codeborne.selenide.Selenide.*;
import static jp.jasminesoft.jfc.test.support.selenide.Operations.*;
import static jp.jasminesoft.util.ExcelFunction.*;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import java.util.Date;
import com.codeborne.selenide.Configuration;
import com.codeborne.selenide.WebDriverRunner;
import jp.jasminesoft.jfc.test.support.selenide.DatePicker;
import jp.jasminesoft.wagby.tests.models.customer.Customer;
import jp.jasminesoft.wagby.tests.models.customer.Customer.Report;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runners.MethodSorters;
/**
 * 1. Customer モデルのテスト
 *
 * @author JasmineSoft
 * @version $Revision$ $Date$
 */
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class CustomerTest {
    /** customer */
    private final Customer model = new Customer();
    /**
     * テストの前処理を行います。
     */
    @BeforeClass
    public static void setUpBeforeClass() throws Exception {
        // アクセスする URL のベース
        Configuration.baseUrl = "http://localhost:8921/wagby";
        // 利用するブラウザ
        Configuration.browser = WebDriverRunner.CHROME;
        // Selenium(WebDriver) 用ドライバファイル
        System.setProperty("webdriver.chrome.driver",
                "customize/chromedriver.exe");
    }
    /**
     * 各テスト実行後の後処理を行います。
     */
    @AfterClass
    public static void tearDownAfterClass() throws Exception {
    }
    /**
     * ログオン処理のテスト
     */
    @Test
    public void test01ログオンのテスト() {
        // ログオン処理を行う。
        //Operations.logon("admin", "admin");
        logon("admin", "wagby");
        // ページタイトルを確認
        pageTitle().shouldHave(exactText("メニュー"));
    }
    /**
     * ログオフ処理のテスト
     */
    @Test
    public void test02ログオフのテスト() {
        // ログオフ処理を行う。
        logoff();
        // checkNoErrors(); // 不要。
        // HTMLのタイトルを確認
        assertThat(title(), is("Wagby アプリケーション ログオン"));
    }
    /**
     * メニューのテスト
     */
    @Test
    public void test04メニューのテスト() {
        // ログオン処理を行う。
        logon("admin", "wagby");
        // ページタイトルを確認
        pageTitle().shouldHave(exactText("メニュー"));
        // 「サービス」タブを選択後、「顧客検索」をクリックする
        selectMenu("サービス", "顧客情報検索");
        pageTitle().shouldHave(exactText("顧客情報 検索"));
        // メニュー画面に戻る。
        menu();
        pageTitle().shouldHave(exactText("メニュー"));
        // サブメニューでの画面遷移
        // 大項目「サービス」を選択後、プルダウンから「顧客情報検索」を選択する
        selectSubMenu("サービス", "顧客情報検索");
        pageTitle().shouldHave(exactText("顧客情報 検索"));
    }
    /**
     * 登録画面への遷移のテスト
     */
    @Test
    public void test05登録画面への遷移テスト() {
        // 「登録画面へ」ボタンをクリック
        //clickNewButton("customer");
        // 登録画面用ボタンが一つの場合は"customer"は省略可
        clickNewButton();
        //clickEditButton(); //更新用(ルールは「登録画面へ」のボタンと同じ)
        pageTitle().shouldHave(exactText("顧客情報 新規登録"));
    }
    /**
     * 保存処理のテスト
     */
    @Test
    public void test06保存処理のテスト() {
        // 保存させるために必須項目に値を入力。
        model.name.val("a");
        // 「保存」ボタンをクリック
        save();
        // ページタイトルを確認
        pageTitle().shouldHave(exactText("顧客情報 詳細表示"));
    }
    /**
     * キャンセル処理のテスト
     */
    @Test
    public void test07キャンセル処理のテスト() {
        // まず、登録画面へ遷移する。
        clickNewButton();
        pageTitle().shouldHave(exactText("顧客情報 新規登録"));
        // 「キャンセル」ボタンをクリック
        // 「キャンセル」ボタンクリック後に表示される確認ダイアログも
        // 自動的にOKをクリックします。
        cancel();
        // ページタイトルを確認
        pageTitle().shouldHave(exactText("顧客情報 詳細表示"));
    }
    /**
     * 登録画面での各項目への入力テスト
     */
    @Test
    public void test08登録画面での各項目への入力テスト() {
        // 登録画面へ遷移する。
        clickNewButton();
        pageTitle().shouldHave(exactText("顧客情報 新規登録"));
        // customerモデルのname項目への入力
        model.name.val("ジャスミン太郎");
        // 「ジャスミン太郎」と入力されていることを確認
        model.name.shouldHave("ジャスミン太郎");
        // ラジオボタン:「地方公共団体」を選択する
        model.customertype.val("地方公共団体");
        // 繰返し項目
        // 繰り返し項目「email」を操作するオブジェクトを作成。
        model.email
                //.clear() // 既存の入力値全てを削除
                .get(1) // 1番目の入力フィールドを取得
                .val("taro@jasminesoft.co.jp")
                .add()  // 「追加」ボタンをクリック
                .val("sales@jasminesoft.co.jp");
        // こちらでも可(内部処理は同じ)
        model.email.val("taro@jasminesoft.co.jp", "sales@jasminesoft.co.jp");
        // 繰り返しコンテナを操作するオブジェクトを作成。
        // コンテナの1行目があればこれを取得する(なければエラー)。
        Report report01 = model.report.get(1);
        // コンテナ1行目の「rnote」項目への入力
        report01.rnote.val("Wagby 購入");
        DatePicker<?> datePicker = report01.rdate.datePicker();
        datePicker.nextMonth();   // 翌月へ
        datePicker.prevMonth();   // 前月へ
        datePicker.nextYear();    // 翌年へ
        datePicker.prevYear();    // 前年へ
        datePicker.selectDate(1); // 1日を選択
        // コンテナの2行目がない状態で実行してもエラーとなる。
        //WebContainerModelitem report02 = report.get(2);
        // コンテナの「追加」ボタンをクリックして、新規行を取得。
        Report report02 = model.report.add();
        datePicker = report02.rdate.datePicker();
        datePicker.nextMonth(); // 翌月へ
        datePicker.prevMonth(); // 前月へ
        datePicker.nextYear(); // 翌年へ
        datePicker.prevYear(); // 前年へ
        datePicker.selectDate(2); // 1日を選択
        // コンテナ2行目の「rnote」項目への入力
        report02.rnote.val("Wagby 保守契約更新");
        // 追記型リストボックス
        // 直接入力を行う場合(存在しない選択肢も可)
        model.companyname.val("ジャスミンソフト");
        // ドロップダウンから選択する場合
        // 存在しない選択肢を選んだ場合はエラー
        //model.companyname
        //    .dropdown()
        //    .select("ジャスミンソフト");
        // 読込専用項目の確認:最終更新者
        model.updateuserid
                .shouldBeReadOnly()
                .shouldHave("admin");
        // 読込専用項目の確認:データ作成日
        model.insertrec
                .shouldBeReadOnly()
                .shouldHave(text(TEXT(NOW(), "yyyy-MM-dd HH:mm:ss")));
        // 読込専用項目の確認:データ更新日
        model.updaterec
                .shouldBeReadOnly()
                .shouldHave(text(TEXT(NOW(), "yyyy-MM-dd HH:mm:ss")));
        // 「保存」ボタンをクリック
        save();
        // ページタイトルを確認
        pageTitle().shouldHave(exactText("顧客情報 詳細表示"));
    }
    /**
     * 更新画面での各項目への入力テスト
     */
    @Test
    public void test09更新画面での各項目への入力テスト() {
        // 更新画面へ遷移する。
        clickEditButton();
        pageTitle().shouldHave(exactText("顧客情報 更新"));
        // customerモデルのkananame項目への入力
        model.kananame.val("ジャスミンタロウ");
        model.companykananame.val("ジャスミンソフト");
        model.deptname.val("技術開発部");
        model.title.val("一般");
        model.zipcode.val("9012227");
        model.address.val("沖縄県宜野湾市宇地泊902-1");
        model.tel.val("098-890-6036");
        model.fax.val("098-890-6038");
        // 繰り返しコンテナを操作するオブジェクトを作成。
        model.report.get(1).rstatus.val("A");
        model.report.get(2).rstatus.val("A");
        // 「保存」ボタンをクリック
        save();
        // ページタイトルを確認
        pageTitle().shouldHave(exactText("顧客情報 詳細表示"));
    }
    /**
     * 詳細画面での各項目の値の確認テスト
     */
    @Test
    public void test10詳細画面での各項目の入力値の確認テスト() {
        // ページタイトルを確認
        pageTitle().shouldHave(exactText("顧客情報 詳細表示"));
        // customerモデルのname項目の値の確認
        model.name.shouldHave("ジャスミン太郎");
        // ラジオボタン
        model.customertype.shouldHave("地方公共団体");
        // 繰返し項目
        model.email.shouldHave(
                "taro@jasminesoft.co.jp", "sales@jasminesoft.co.jp");
        // コンテナの1行目を取得。
        Report report01 = model.report.get(1);
        Date bomonth = BOMONTH(NOW());
        String dateText = TEXT(bomonth, "yyyy-MM-dd");
        // コンテナ1行目の「rnote」項目への入力
        report01.rnote.shouldHave("Wagby 購入");
        report01.rdate.shouldHave(dateText);
        // コンテナの2行目を取得。
        Report report02 = model.report.get(2);
        dateText = TEXT(MOVEDAY(bomonth, 1), "yyyy-MM-dd");
        report02.rdate.shouldHave(dateText);
        // コンテナ2行目の「rnote」項目の値の確認
        report02.rnote.shouldHave("Wagby 保守契約更新");
        // 追記型リストボックス
        model.companyname.shouldHave("ジャスミンソフト");
        // 読込専用項目の確認:最終更新者
        model.updateuserid.shouldHave("admin");
        // 読込専用項目の確認:データ作成日
        model.insertrec.shouldHave(text(TEXT(NOW(), "yyyy-MM-dd HH:mm:ss")));
        // 読込専用項目の確認:データ更新日
        model.updaterec.shouldHave(text(TEXT(NOW(), "yyyy-MM-dd HH:mm:ss")));
    }
    /**
     * 詳細画面での各項目の値の確認テスト2
     */
    @Test
    public void test11詳細画面での各項目の入力値の確認テスト2() {
        // ページタイトルを確認
        pageTitle().shouldHave(exactText("顧客情報 詳細表示"));
        // customerモデルのkananame項目への入力
        model.kananame.shouldHave("ジャスミンタロウ");
        model.companykananame.shouldHave("ジャスミンソフト");
        model.deptname.shouldHave("技術開発部");
        model.title.shouldHave("一般");
        model.zipcode.shouldHave("9012227");
        model.address.shouldHave("沖縄県宜野湾市宇地泊902-1");
        model.tel.shouldHave("098-890-6036");
        model.fax.shouldHave("098-890-6038");
        model.report.get(1).rstatus.shouldHave("A");
        model.report.get(2).rstatus.shouldHave("A");
    }
    /**
     * ログオフ
     */
    @Test
    public void test99ログオフ() {
        logoff();
    }
}

ダウンロード

WTFモデルクラスの概念を適用したテストコードをダウンロードできます。

WTFモデルクラスの自動生成8.5.0

R8.5.0より、日本語でテストシナリオを用意すると、テストコードを自動生成する機能が提供されました。[マニュアルページへのリンク...]

ここで自動生成されるコードには、上で説明した WTF モデルクラスも含まれます。開発者は WTF モデルクラスを手動で作成するのではなく、自動生成されたソースコードを再利用してください。