ログ日記

作業ログと日記とメモ

ゼロから始めるGWT その2

その1の続き。

  • インターフェースの定義
  • 実装クラスの作成
  • テストの作成
  • 実装


サンプルに足し算フォームを追加してみる。


war/Hoge.htmlにフォーム用のHTMLを見様見真似で追加する。

    <table align="center">
      <tr>
        <td colspan="2" style="font-weight:bold;">足し算:</td>        
      </tr>
      <tr>
        <td id="calcFieldsContainer"></td>
        <td id="calcButtonContainer"></td>
      </tr>
      <tr>
        <td colspan="2" style="color:red;" id="errorCalcLabelContainer"></td>
      </tr>
    </table>

保存したら、Development Mode でローカルホストを表示しているブラウザをリロードしたらすぐに反映されているのがわかる。
まだフォームが表示されていないので src/hoge.client/Hoge.java を開いて編集する。

...
	public void onModuleLoad() {
...
		final Button calcButton = new Button("Calc");
		final TextBox field1 = new TextBox();
		final TextBox field2 = new TextBox();
		final Label errorCalcLabel = new Label();
		calcButton.addStyleName("sendButton");
		RootPanel.get("calcFieldsContainer").add(field1);
		RootPanel.get("calcFieldsContainer").add(field2);
		RootPanel.get("calcButtonContainer").add(calcButton);
		RootPanel.get("errorCalcLabelContainer").add(errorCalcLabel);


これでフォームが表示される。
Vistaだとボタンラベルを日本語にしたら文字化けした。Debianだと文字化けしなかった。
final は無名クラスを利用するときに必要らしい。おまじないのように書いておく。




次に足し算用のインターフェースを定義する。
サーバー用インターフェースとしてCalcService、クライアントが呼び出す用のインターフェースとしてCalcServiceAsync、実装としてCalcServiceImplを作ることにする。
インターフェースはクライアント側(src/hoge.client/CalcService.java)に作り、実装はサーバー側(src/hoge.server/CalcServiceImpl.java)に作る。


まずhoge.clientを右クリックして「新規」「インターフェース」で名前にCalcServiceと入力して完了。
内容を書く。

package hoge.client;

import com.google.gwt.user.client.rpc.RemoteService;
import com.google.gwt.user.client.rpc.RemoteServiceRelativePath;


@RemoteServiceRelativePath("calc")
public interface CalcService extends RemoteService {
	int calc(int a, int b) throws IllegalArgumentException;
}

RemoteServiceRelativePathはRPC呼び出しの時に使うPATHだと思う。一意の必要がありそうな予感がする。
この内容を入力したらCalcServiceに波線が表示されて、CalcServiceAsyncがありませんよとエラーが出るので、同じ要領でCalcServiceAsyncを作る。
そうするとエラーが出てcalcメソッドがないので指示通り追加するとAsyncCallbackがないとエラーが出てポップアップをクリックするとimport文が追加される。なんかすごいね。
AsyncCallbackの方のCallbackの型はIntegerになるようだ。throwsは警告が出ないので手動で付け足し。

    public void calc(int a, int b AsyncCallback<Integer> callback) throws IllegalArgumentException;
<Integer>

というのは使ったことはないが、C++のテンプレートみたいなものなんだろう。C++も使ったことないが。


次にhoge.serverにCalcServiceImplクラスを作る。

public class CalcServiceImpl extends RemoteServiceServlet implements CalcService{

ここまで書いたら、あとはエラー表示をクリックしていくだけでコードが出来上がっていく。
とりあえず

	public int calc(int a, int b) throws IllegalArgumentException {
		return 0;
	}

これはそのままにしておいて、せっかくなのでテストを作ってみよう。


プロジェクト・エクスプローラでの右クリックで「実行」→「実行の構成」→「GWT JUnit Test」右クリック「新規」。何となくJUnit4を選んで適用。
[テスト]: クラス 'junit.framework.TestCase' がプロジェクトのビルド・パスで見つかりません。とエラーが出るので、「ビルド・パス」→「ビルド・パスの構成」→「ライブラリー」→「ライブラリーの追加」→「JUnit」。


test/hoge.clientに CalcServiceTestクラスを追加する。
クラス名の後ろに extends GWTTestCase を追加してエラーを解決してgetModuleNameが返す値を "hoge" にしてみる。


ここでCalcServiceTest.javaを右クリックして「実行」→「JUnit テスト」。
No tests founds エラーが出るので、テストを追加。

	public void testTrue(){
		assertTrue(true);
	}

Unable to find 'hoge.gwt.xml' というエラーが出る。おそらくsrc/hoge/Hoge.gwt.xml のことだろうと思うので、getModuleNameが返す値をhoge.Hogeに変更してリトライ。まだクラスが見つからないというようなエラーが出る。
クラスが見えないようなので「実行の構成」→「JUnit」→「CalcServiceTest」→「クラスパス」→「ユーザー・エントリー」→「拡張」→「フォルダーの追加」でsrcとtestを追加する。


ここまででassertTrueでグリーンを表示させるまでは完了。
testCalcメソッドを書く。ここも折角なので日本語でいってみよう。

        public void test足し算(){
                Timer timer = new Timer(){
                        public void run(){
                                CalcServiceAsync calc = (CalcServiceAsync)GWT.create(CalcService.class);
                                AsyncCallback<Integer> callback = new AsyncCallback<Integer>(){

                                        @Override
                                        public void onFailure(Throwable caught) {
                                                // TODO 自動生成されたメソッド・スタブ
                                                fail(caught.getMessage());
                                        }

                                        @Override
                                        public void onSuccess(Integer result) {
                                                // TODO 自動生成されたメソッド・スタブ
                                                assertEquals(result, (Integer)3);
                                                finishTest();
                                        }

                                };
                                calc.calc(1, 2, callback);
                        }
                };
                delayTestFinish(2000);
                timer.schedule(100);
        }

Timerなどは

入門Google Web Toolkit

入門Google Web Toolkit

これを参考にしつつ。バージョンが違うからか、ちょっと書き方が違うけれども。


それでテストを実行したら

[WARN] Resource not found: calc; ・・・

とエラーが出る。
RemoteServiceRelativePath("calc")の関連する設定が必要らしい。


src/hoge/Hoge.gwt.xml を開いて

  <servlet path='/calc' class='hoge.server.CalcServiceImpl' />

を付け足し再度テストを実行。
ここでようやく期待通りのエラー

expected=0 actual=3

が出たのでCalcServiceImplの実装を変更する。

        public int calc(int a, int b) throws IllegalArgumentException {
                // TODO 自動生成されたメソッド・スタブ
                return 3;
        }

これでオールグリーンになった。
三角測量をしようと思ったのだが、テストメソッドの長い非同期コードを全部コピペしないといけない?

        public void test足し算2(){
                Timer timer = new Timer(){
                        public void run(){
                                CalcServiceAsync calc = (CalcServiceAsync)GWT.create(CalcService.class);
                                AsyncCallback<Integer> callback = new AsyncCallback<Integer>(){

                                        @Override
                                        public void onFailure(Throwable caught) {
                                                // TODO 自動生成されたメソッド・スタブ
                                                fail(caught.getMessage());
                                        }

                                        @Override
                                        public void onSuccess(Integer result) {
                                                // TODO 自動生成されたメソッド・スタブ
                                                assertEquals(result, (Integer)5);
                                                finishTest();
                                        }

                                };
                                calc.calc(2, 3, callback);
                        }
                };
                delayTestFinish(2000);
                timer.schedule(100);
        }

うーん。これで期待通りエラーになったがかなり無駄な感じ。


書き方が分からないが取り敢えずコンパイルエラーをなくしつつまとめてみる。

    public void test足し算(){
        class TimerGetter {
            private int count = 0;
            private int num = 0;
            public TimerGetter(int n){
                num = n;
            }
            public Timer getTimer (final int a, final int b, final int ret){
                return new Timer(){
                    public void run(){
                        CalcServiceAsync calc = (CalcServiceAsync)GWT.create(CalcService.class);
                        AsyncCallback<Integer> callback = new AsyncCallback<Integer>(){

                            @Override
                            public void onFailure(Throwable caught) {
                                // TODO 自動生成されたメソッド・スタブ
                                fail(caught.getMessage());
                                count++;
                            }

                            @Override
                            public void onSuccess(Integer result) {
                                // TODO 自動生成されたメソッド・スタブ
                                assertEquals(result, (Integer)ret);
                                count++;
                                if (count >= num)
                                    finishTest();
                            }

                        };
                        calc.calc(a, b, callback);
                    }
                };
            }
        }
        TimerGetter getter = new TimerGetter(2);
        Timer timer1 = getter.getTimer(1, 2, 3);
        Timer timer2 = getter.getTimer(2, 3, 5);
        delayTestFinish(2000);
        timer1.schedule(100);
        timer2.schedule(100);
    }

ローカルクラスのプライベートプロパティをその内部のクラスから参照できるんだっけ?非同期処理が絡んでいるのにこんなコードでいいのかな?という疑問は残しつつ次へ。
これで一応三角測量まで完成。実装の方をa + bにしてテストクリア。


次はいよいよフォームボタンを押したときの動作をHoge.javaに書く。