SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

オイシックスの事例から学ぶ、JavaのWebアプリに自動テストを導入する方法

オイシックスの独自フレームワークをモデルにしたサンプルWebアプリケーション

オイシックスの事例から学ぶ、JavaのWebアプリに自動テストを導入する方法 第2回

アプリケーションアーキテクチャの説明

 サンプルアプリケーションは冒頭で説明したとおり、MVCモデルで作られています。

 簡単に説明すると、サーブレット(Controller)を1本にし、ビジネスロジック部分のみをModelに実装して、表示はView(jsp)に任せるという効率的なアプリケーションの作り方になると思います。

 次に上記の図について説明をしようと思います。まず、左からControllerにあたるものが、com.oisix.sample.base.ControllerServlet.javaになります。ControllerServletはweb.xmlでwelcomeというurlとマッピングされています。中身はシンプルなサーブレットですが、少し分かりづらい部分がありますので、ソースを説明したいと思います。

 ControllerServletは、リクエストパラメータの「model」というキー名で指定されたModelを呼び出す作りになっています。もし該当のModelがない場合は、エラー画面に遷移します。

 続いて、Modelの説明になります。ModelはModelBeanインターフェースを実装し、下記のソースコードの太字部分のようにControllerServletから

  • init(HttpServletRequest request, HttpServletResponse response)…初期化を処理を実装。
  • validate()…入力チェックなどのバリデーション処理を実装。
  • process()…データベースアクセスなどのビジネスロジックを実装。

のメソッドが順番に呼び出されるようになっています。Modelの処理が完了すると、該当のjspにforwardされます。

String name = request.getParameter("model");

ModelBean modelBean = null;
try {
	modelBean = (ModelBean)Class.forName(modelPackage + name + "Bean").newInstance();

} catch (Exception e) {} // log省略

if (modelBean == null) {
    forward("error.jsp", request, response);
} else {
    modelBean.init(request, response);
    modelBean.validate();
    modelBean.process();
    request.setAttribute("bean", modelBean);
    if (modelBean.isSystemError()) {
        forward("error.jsp", request, response);
    } else {
        forward(name + ".jsp", request, response);
    }
}

 ViewはWebContent/WEB-INF/jsp以下のjspになります。

 データベースアクセスについては、Daoはcom.oisix.sample.dao以下のクラスで行い、O/Rマッパーにhibernateを使用しています。Hibernateのエンティティはcom.oisix.sample.model以下に置いてあります。

テスト対象クラスの説明

 テスト対象ソースの説明をしたいと思います。すべて掲載すると長くなってしまうので、一部省略してあります。

 説明については、ソースにコメントを付けましたので、確認いただければと思います。

com.oisix.sample.bean.CustomerSearchBean.java(顧客検索)
public class CustomerSearchBean extends ModelBeanBase {
    ・
    ・省略
    ・
    @Override
    public void validate() throws IOException, ServletException {}

    @Override
    public void process() {
        // isClick()メソッドは、ブラウザで[検索]ボタンが押されてたことを判断するメソッドです。
        // ”search”というキーがリクエストパラメーターに含まれているか確認して[検索]ボタンが
        // 押されたことを判断しています。
        if (isClick("search")) {
            this.search();
        }
    }

    /**
     * 顧客を検索します。
     */
    public void search() {
        getParameter();

        // hibernateのセッションを作っている部分です。
        Configuration configuration = new Configuration();
            ・
            ・省略
            ・
        try {
            factory.getCurrentSession().beginTransaction();

            // Daoで顧客情報を検索しています。このfind()メソッドの中でsqlを発行しています。
            // 結果はresultというフィールド変数に参照させて、jspで表示するときに使います。            
            this.result = MstCustomerDao.find(factory.getCurrentSession(), this.searchCondition);
            
            factory.getCurrentSession().getTransaction().commit();
            
        } catch (RuntimeException e) {
            factory.getCurrentSession().getTransaction().rollback();
        } finally {
            factory.getCurrentSession().close();
        }
    }

    /**
     * リクエストパラメータを取得します。
     */
    private void getParameter() {
        // このメソッドではブラウザから送信された検索条件をMstCustomerにセットしています。
        this.searchCondition.setCustomerId(getParameterNvl("customerId"));
        this.searchCondition.setFullname(getParameterNvl("fullname"));
            ・
            ・省略
            ・
    }

    ・
    ・省略
    ・

}

 上記に掲載しているソースは、顧客検索画面のソースです。基本的な作りとしてvalidate()、process()メソッドで構成されているクラスです。validate()に入力チェックに関する処理を実装して、process()メソッドに画面に表示する項目をデータベースから取得してくるようなビジネスロジックを実装します。詳しい説明については、上記ソースのコメントとして記述しましたので、そちらをご確認ください。

com.oisix.sample.bean. CustomerEditBean.java(顧客登録・更新画面)
public class CustomerEditBean extends ModelBeanBase {
        ・
        ・省略
        ・
    @Override
    public void validate() {

        // isClick()メソッドは、ブラウザで[登録]または、[更新]ボタンが押されてたことを判定するメソッドです。
        // "edit"というキーがリクエストパラメーターに含まれているか確認して[確認]ボタンが
        // 押されたことを判定しています。
        if (isClick("edit")) {
            // リクエストパラメータから登録・更新情報を取得します。
            this.getParameter();
            // エラーメッセージ用のListを取得します。
            List<String> errors = super.getErrors();

            // 登録・更新する情報に対して入力チェックを行います。
            new FullnameValidator(errors).validate(this.mstCustomer.getFullname());
            new FullnameKanaValidator(errors).validate(this.mstCustomer.getFullnameKana());
            new MailAddressValidator(errors).validate(this.mstCustomer.getMailAddress());
            new ZipCodeValidator(errors).validate(this.mstCustomer.getZipCode1() + "-" + this.mstCustomer.getZipCode2());
            new TodofukenValidator(errors).validate(this.mstCustomer.getTodofuken());
            new Address1Validator(errors).validate(this.mstCustomer.getAddress1());
            new Address2Validator(errors).validate(this.mstCustomer.getAddress2());
            new TelValidator(errors).validate(this.mstCustomer.getTel1(), this.mstCustomer.getTel2(), this.mstCustomer.getTel3());
        }
        if (errors.size() == 0) {
            // エラーがなかった場合の処理
            // 郵便番号と電話番号は画面ではテキストボックスを分けて入力していますが
            // 郵便番号:[xxx]-[xxxx]、電話番号:[xxx]-[xxxx]-[xxxx]
            // データベースのカラムでは、1カラムになっているので結合しています。
            this.mstCustomer.joinZipCode();
            this.mstCustomer.joinTel();
        }
        // エラーをフィールド変数にセットする。
        super.setErrors(errors);
    }


    @Override
    public void process() {
        this.mstCustomer.setCustomerId(getParameterNvl("customerId"));
        
        // isClick()メソッドは、ブラウザで[登録]または、[更新]ボタンが押されてたことを判定するメソッドです。
        // "edit"というキーがリクエストパラメーターに含まれているか確認して[確認]ボタンが
        // 押されたことを判定しています。
        if (isClick("edit")) {
            this.edit();
        }
        // こちらのロジックはリクエストパラメータにcustomerIdを含むか判断して
        // 含まれる場合は顧客情報検索のsearch()メソッドを呼び出す処理です。
        // 更新モードでこの画面クラスが呼ばれた場合は、リクエストパラメータでcustomerIdを送信するようにしています。
        else if (!StringUtils.isEmpty(this.mstCustomer.getCustomerId())) {
            search();
            request.setAttribute("mstCustomer", this.mstCustomer);
        }
    }

    public void search() {
        Configuration configuration = new Configuration();
        configuration.configure();
        ServiceRegistry serviceRegistry = new ServiceRegistryBuilder().applySettings(configuration.getProperties()).buildServiceRegistry();
        SessionFactory factory = configuration.buildSessionFactory(serviceRegistry);

        try {
            // トランザクション開始
            factory.getCurrentSession().beginTransaction();
            
            // Daoで顧客情報を検索しています。このfindByPk()メソッドの中でsqlを発行されています。
            // 結果はmstCustomerというフィールド変数に参照させて、jspで表示するときに使います。
            this.mstCustomer = MstCustomerDao.findByPk(
                factory.getCurrentSession(), mstCustomer.getCustomerId()
            );
            this.mstCustomer.divideZipCode();
            this.mstCustomer.divideTel();
            
            // コミット
            factory.getCurrentSession().getTransaction().commit();
            
        } catch (RuntimeException e) {
            // ロールバック
            factory.getCurrentSession().getTransaction().rollback();
        } finally {
            // セッションをクローズ
            factory.getCurrentSession().close();
        }
    }

    public void edit() {
        // 入力エラーがあるか判定
        // 入力エラーがある場合は処理終了
        if (super.isError()) {return;}

        Configuration configuration = new Configuration();
        configuration.configure();
        ServiceRegistry serviceRegistry = new ServiceRegistryBuilder().applySettings(configuration.getProperties()).buildServiceRegistry();
        SessionFactory factory = configuration.buildSessionFactory(serviceRegistry);

        try {
            // トランザクション開始
            factory.getCurrentSession().beginTransaction();
        
            // セッションにマージ
            MstCustomerDao.merge(factory.getCurrentSession(), mstCustomer);

            // コミット
            factory.getCurrentSession().getTransaction().commit();
            super.setActionMessage(getViewTitle() + "完了しました。");
            
        } catch (RuntimeException e) {
            // ロールバック
            factory.getCurrentSession().getTransaction().rollback();
            super.setActionMessage(getViewTitle() + "に失敗しました。");
        } finally {
            // セッションをクローズ
            factory.getCurrentSession().close();
        }
    }
        ・
        ・省略
        ・
}

 上記に掲載しているソースは、顧客登録・更新画面のソースです。こちらも基本的な作りとしてvalidate()、process()メソッドで構成されているクラスです。validate()に入力チェックに関する処理を実装して、process()メソッドに画面に表示する項目をデータベースから取得してくるようなビジネスロジックを実装します。詳しい説明については、上記ソースのコメントとして記述しましたので、そちらをご確認ください。

ソースのディレクトリ構成
<<ソースのディレクトリ構成>>

 簡単ではありますが、上記がサンプルWebアプリケーションのディレクトリ構成になります。

  • build」…ビルドしたファイルが出力されます。Eclipseの.classpathで使用しているディレクトリです。
  • src」…hibernateの設定ファイルの他、javaファイルが置いてあります。
  • Web-Content/images」…画像ファイルが置いてあります。
  • Web-Content/META-INF」…MANIFESTファイルが置いてあります。
  • Web-Content/WEB-INF」…web.xmlファイル、jspの中にはjspファイル。libの中には主にhibernateに必要なjarが入っています。

次のページ
サンプルアプリケーションのリポジトリをクローンしてプロジェクトを作る

この記事は参考になりましたか?

オイシックスの事例から学ぶ、JavaのWebアプリに自動テストを導入する方法連載記事一覧

もっと読む

この記事の著者

山田 昌平(オイシックス株式会社)(ヤマダ ショウヘイ)

オイシックスでは 運用→開発→開発推進を担当してきました。あと、ITイベントの協賛も担当しております。イベントは、技術的な面、エンジニアとしての考え方など、本当に学ぶことが多いのでおすすめです。お会いすることもあると思いますので気軽に声をお掛けください。

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/7569 2014/01/27 14:00

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー