ゼロから始める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などは
- 作者: 吉野雅人,江川崇,竹端進
- 出版社/メーカー: ソフトバンククリエイティブ
- 発売日: 2006/12/28
- メディア: 単行本
- 購入: 1人 クリック: 24回
- この商品を含むブログ (17件) を見る
それでテストを実行したら
[WARN] Resource not found: calc; ・・・
とエラーが出る。
RemoteServiceRelativePath("calc")の関連する設定が必要らしい。
<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にしてテストクリア。