【入門編】Slim3で始める!GAE/JでWebアプリケーション開発 (第2回)

Controllerの作成

記事登録を行うControllerの実装

記事登録は、先に作った記事作成フォームの入力値を受け取り、データストアに保存する処理を行います。登録前に入力値を検証して、NGだった場合には入力画面に戻り、OKだった場合は保存してトップの記事一覧にリダイレクトします。


PostEntryControllerイメージ

今回はJSPの実装はないので、Controllerの実装がメインになります。また、入力値の検証にはSlim3のバリデーションが非常に便利なのであわせて紹介します。

  • Antタスク gen-controller-without-view の実行今回はJSPは不要なので gen-controller-without-view を実行します。プロンプトに /bbs/postEntry と記述しOKボタンを押下します。gen-controller-without-viewイメージすると、/bbs/postEntry に対応する
    • simplebbs.controller.bbs.PostEntryController
    • simplebbs.controller.bbs.PostEntryControllerTest

    が生成されます。

  • テストケースの作成PostEntryController はバリデーション(入力値の検証)と記事の登録を行うので、この辺りを網羅的にテストする必要があります。アプリ要件である各入力フォームの検査内容は以下の通りとします。
    入力フォーム 検証内容
    タイトル 入力必須、50文字までであること
    お名前 入力必須、50文字までであること
    本文 入力必須であること (文字数制限なし)
    編集用パスワード 未入力可能、ただし入力がある場合は6~20文字までであること

    これらのバリデーションをテストするために PostEntryControllerTest を以下のように修正しました。

    PostEntryControllerTest.java

    package simplebbs.controller.bbs;
    
    import org.slim3.tester.ControllerTestCase;
    import org.junit.Test;
    import simplebbs.service.bbs.BbsService;
    import static org.junit.Assert.*;
    import static org.hamcrest.CoreMatchers.*;
    
    public class PostEntryControllerTest extends ControllerTestCase {
    
        // バリデーションOKな入力値
        private static final String SUBJECT = "タイトルです";
        private static final String USERNAME = "投稿者名です";
        private static final String TEXT = "本文です。";
        private static final String PASSWORD = "password";
        // Service
        private BbsService service = new BbsService();
    
        @Test
        public void testValidParameters() throws Exception {
            // -----<< すべての入力値OKのテスト(パスワードあり) >>----- //
            PostEntryController controller =
                postEntry(SUBJECT, USERNAME, TEXT, PASSWORD);
            // ========== assertion start ========== //
            assertThat(controller, is(notNullValue()));
            assertValidParameters(); // 入力値OK
            // ========== assertion end ========== //
        }
        // 記事作成ページでPOSTした動作をエミュレート
        private PostEntryController postEntry(String subject, String username, String text, String password) throws Exception {
            tester.param("subject", subject);
            tester.param("username", username);
            tester.param("text", text);
            tester.param("password", password);
            tester.request.setMethod("POST");
            tester.start("/bbs/postEntry");
            return tester.getController();
        }
        // バリデーションOKな入力値であること
        private void assertValidParameters() {
            // 1件の記事が登録されていること
            assertThat(service.getAll().size(), is(1));
            // エラーメッセージが1つもセットされていないこと
            assertThat(tester.getErrors().isEmpty(), is(true));
            // トップページにリダイレクトしていること
            assertThat(tester.getDestinationPath(), is("/bbs/"));
            assertThat(tester.isRedirect(), is(true));
        }
        // バリデーションNGな入力値であること
        private void assertInvalidParameters() {
            // 記事が登録されていないこと(0件であること)
            assertThat(service.getAll().size(), is(0));
            // エラーメッセージが何かしらセットされていること
            assertThat(tester.getErrors().isEmpty(), is(false));
            // 記事作成ページにforwardしていること
            assertThat(tester.getDestinationPath(), is("/bbs/create"));
            assertThat(tester.isRedirect(), is(false));
        }
        @Test
        public void testValidParametersWithoutPassword() throws Exception {
            // -----<< すべての入力値OKのテスト(パスワードなし) >>----- //
            postEntry(SUBJECT, USERNAME, TEXT, "");
            // ========== assertion start ========== //
            assertValidParameters(); // 入力値OK
            // ========== assertion end ========== //
        }
        @Test
        public void testSubjectEmpty() throws Exception {
            // -----<< タイトル未入力で入力値NGのテスト >>----- //
            postEntry("", USERNAME, TEXT, PASSWORD);
            // ========== assertion start ========== //
            assertInvalidParameters(); // 入力値NG
            // subject のエラーメッセージがセットされていること
            assertThat(tester.getErrors().get("subject"), is(notNullValue()));
            // ========== assertion end ========== //
        }
        @Test
        public void testSubjectLentgh51() throws Exception {
            // -----<< タイトル文字数51文字で入力値NGのテスト >>----- //
            String char51 =
                "0123456789" +
                "0123456789" +
                "0123456789" +
                "0123456789" +
                "0123456789" +
                "0";
            postEntry(char51, USERNAME, TEXT, PASSWORD);
            // ========== assertion start ========== //
            assertInvalidParameters(); // 入力値NG
            // subject のエラーメッセージがセットされていること
            assertThat(tester.getErrors().get("subject"), is(notNullValue()));
            // ========== assertion end ========== //
        }
        @Test
        public void testSubjectLength50() throws Exception {
            // -----<< タイトル文字数50文字で入力値OKのテスト >>----- //
            String char50 =
                "0123456789" +
                "0123456789" +
                "0123456789" +
                "0123456789" +
                "0123456789";
            postEntry(char50, USERNAME, TEXT, PASSWORD);
            // ========== assertion start ========== //
            assertValidParameters(); // 入力値OK
            // ========== assertion end ========== //
        }
    
        // ... 以降のテスト省略
    
    }
    

    4つの入力フォームについて網羅的にバリデーションのテストを行います。テスト数が多いため上記のソースはタイトルの入力フォームのテストのみ抜粋し、他は省略しました。

    Controllerのテストでは1つのテストメソッドに1リクエスト分のテストを記述します。これは、テストメソッド毎にリクエストスコープやデータストアなどが初期化されるので、1テストメソッド=1リクエストとすることで、前のテストでリクエストスコープやデータストアがどういう状態になっているかを意識しなくて済みます。むしろ、今回のように入力値を変えて何度もテストする場合、前回のテストの入力値やエラーメッセージがリクエストスコープに残っていると正しいテストが行えません。

    それでは、最初のテストについて説明します。

    @Test
    public void testValidParameters() throws Exception {
        // -----<< すべての入力値OKのテスト(パスワードあり) >>----- //
        PostEntryController controller =
            postEntry(SUBJECT, USERNAME, TEXT, PASSWORD);
        // ========== assertion start ========== //
        assertThat(controller, is(notNullValue()));
        assertValidParameters(); // 入力値OK
        // ========== assertion end ========== //
    }
    

    testValidParameters() はすべて有効な入力値をPOSTした場合のテストケースです。この後、入力値を変えて何回もテストを行うので、リクエストを送信する処理は次の postEntry() にまとめました。

    // 記事作成ページでPOSTした動作をエミュレート
    private PostEntryController postEntry(String subject, String username, String text, String password) throws Exception {
        tester.param("subject", subject);
        tester.param("username", username);
        tester.param("text", text);
        tester.param("password", password);
        tester.request.setMethod("POST");
        tester.start("/bbs/postEntry");
        return tester.getController();
    }
    

    tester.param() はリクエストパラメータに値をセットするメソッドです。 tester.request.setMethod() はGET/POSTなど送信するリクエストのメソッドを指定します。記事作成フォームからの入力を想定しているので POST を指定します。そして tester.start() でリクエストを送信し PostEntryController を実行します。

    今回は有効な入力値なので、Controllerの実行結果について以下の検査を行います。

    // バリデーションOKな入力値であること
    private void assertValidParameters() {
        // 1件の記事が登録されていること
        assertThat(service.getAll().size(), is(1));
        // エラーメッセージが1つもセットされていないこと
        assertThat(tester.getErrors().isEmpty(), is(true));
        // トップページにリダイレクトしていること
        assertThat(tester.getDestinationPath(), is("/bbs/"));
        assertThat(tester.isRedirect(), is(true));
    }
    

    検査内容はコメントの通りです。 tester.getErrors() はリクエストスコープ上のErrorsオブジェクトを返します。これは create.jsp の説明でも出てきた errors と同じものです。Slim3のバリデーションを使ってチェックエラーとなった場合には、このErrorsオブジェクトにエラーメッセージがputされているはずなので、今回は空っぽであることを確認しています。

    次に、不正な入力値のテストケース testSubjectEmpty() を見てみます。

    @Test
    public void testSubjectEmpty() throws Exception {
        // -----<< タイトル未入力で入力値NGのテスト >>----- //
        postEntry("", USERNAME, TEXT, PASSWORD);
        // ========== assertion start ========== //
        assertInvalidParameters(); // 入力値NG
        // subject のエラーメッセージがセットされていること
        assertThat(tester.getErrors().get("subject"), is(notNullValue()));
        // ========== assertion end ========== //
    }
    

    タイトルが未入力のなので、バリデーションエラーとなる想定で以下の検査を行います。

    // バリデーションNGな入力値であること
    private void assertInvalidParameters() {
        // 記事が登録されていないこと(0件であること)
        assertThat(service.getAll().size(), is(0));
        // エラーメッセージが何かしらセットされていること
        assertThat(tester.getErrors().isEmpty(), is(false));
        // 記事作成ページにforwardしていること
        assertThat(tester.getDestinationPath(), is("/bbs/create"));
        assertThat(tester.isRedirect(), is(false));
    }
    

    以降のテストは網羅的に入力値を変えて同様なテストしているだけですので説明は省きます。全テストケースを書き終わったら保存して実行してみます。全て失敗するはずです。


    PostEntryControllerTest実行イメージ

    次に、これらのテストケースをクリアするよう PostEntryController を修正します。