ラベル プログラミング の投稿を表示しています。 すべての投稿を表示
ラベル プログラミング の投稿を表示しています。 すべての投稿を表示

2011-03-05

JExcelAPI:GAE/J+Slim3でExcel作成、日本語データを入れてみた

JExcelAPIの続き。
作成するExcelシートのセルに日本語データを入れてみた。
cell = sheet.getWritableCell(0, 0);
Label lbl = new Label(0, 0, "あいうえお");
sheet.addCell(lbl);
ダウンロードしたExcelファイルを開いてみると、ちゃんと設定した値がセルに格納されていた。
なんか簡単すぎて拍子抜けした感じ...

2011-02-11

JExcelAPI:GAE/J+Slim3でExcel作成、ちょっと困った(~_~)

GAE/J+Slim3で作っているアプリでExcelを操作する必要があったので「JExcelAPI」を調べてみた。
ちょっと困ったことが起きたが最終的に目的の動作は確認できたので、その経過をメモしておく。(まだ実際のGAE/J環境で動作確認していない)

とりあえず"tutorial"を読みながら、
  • 新規にExcel Workbookを作成
  • シートを作成
  • セルに値を入力
という処理を作ってみた。
コードはこんな感じ。
ByteArrayOutputStream os = new ByteArrayOutputStream();
WritableWorkbook workbook = Workbook.createWorkbook(os);
WritableSheet sheet = workbook.createSheet("SheetA", 0);
Label label = new Label(0, 0, "ABC");
sheet.addCell(label);
workbook.write();
workbook.close();
ダウンロードしたファイルをExcelで開いてみると、ちゃんと[A1]のセルに"ABC"の文字が入っている。(^^)v
ここでポイントはWorkbookを作成するところで、"tutorial"では
Workbook.createWorkbook(new File("output.xls")); 
とFileオブジェクトを使用している代わりにByteArrayOutputStreamを使用しているところ。
GAE/JではファイルI/O関連の使用に制約がありファイル出力が行えない。したがってファイルへ出力する代わりにByteArrayOutputStreamでバイトストリームに出力する。

本来やりたかったことはテンプレートとなるExcelファイルを用意しておいて、特定のセルに処理結果の値を入力しダウンロードできるようにすること。
次はこの処理を試してみる。
"tutorial"の"Copying and Modifying Spreadsheets"の説明を参考にコードを書いてみた。
String templatePath = servletContext.getRealPath("/template/sample.xls");
File file = new File(templatePath);
Workbook template = Workbook.getWorkbook(file);
ByteArrayOutputStream os = new ByteArrayOutputStream();
WritableWorkbook workbook = Workbook.createWorkbook(os, template);
WritableSheet sheet = workbook.getSheet(0);
WritableCell cell = sheet.getWritableCell(1, 2);
if (cell.getType() == CellType.LABEL) {
  Label l = (Label) cell;
  l.setString("modified cell");
}
workbook.write();
workbook.close();
さっそく実行してみると
java.lang.ArrayIndexOutOfBoundsException
    at java.lang.System.arraycopy(Native Method)
    at jxl.biff.StringHelper.getBytes(StringHelper.java:127)
    at jxl.write.biff.WriteAccessRecord.<init>(WriteAccessRecord.java:59)
    at jxl.write.biff.WritableWorkbookImpl.write(WritableWorkbookImpl.java:726)
    at jp.technosite.election.controller.pp.DlController.getExcelReport(DlController.java:73)
    at jp.technosite.election.controller.pp.DlController.run(DlController.java:41)
とArrayIndexOutOfBoundsExceptionが発生してしまった。
JExcelAPI側で落ちてるし、セルへ値を設定せずにwriteしても同じ結果になったので、JExcelAPI側に問題がありそうだ。
ということでデバッガで追ってみる。

まずは
jxl.write.biff.WriteAccessRecord.<init>(WriteAccessRecord.java:59)
の部分...
public WriteAccessRecord(String userName) {
    super(Type.WRITEACCESS);
    data = new byte[112];
    String astring = userName == null ? (new StringBuilder()).append("Java Excel API v").append(Workbook.getVersion()).toString() : userName;
    StringHelper.getBytes(astring, data, 0);
    for(int i = astring.length(); i < data.length; i++)
        data[i] = 32;
}
"StringHelper.getBytes()"の箇所で落ちている。
ここでは astringを dataにコピーしていて、dataはサイズが112となっている。
ということはastringが怪しい。デバッガで内容を確認すると150byte以上あったのでこれが原因だ。
この元になっているのはメソッド引数 userNameなので呼び元の
jxl.write.biff.WritableWorkbookImpl.write(WritableWorkbookImpl.java:726)
を調べてみると、
WriteAccessRecord wr = new WriteAccessRecord(settings.getWriteAccess());
となっていて settings.getWriteAccess()の値が使われている。
userNameが'null'なら適当な値を設定してくれるので、settings.getWriteAccess()の値をnullになるようにすれば動きそうだ。
settingsはWorkbookSettingsクラスなのでJavadocで調べてみると、setWriteAccess(String)があって
setWriteAccess(null);
とすればよさそうだ。

結局、こんなコードになった。
String templatePath = servletContext.getRealPath("/template/sample.xls");
File file = new File(templatePath);
Workbook template = Workbook.getWorkbook(file);
ByteArrayOutputStream os = new ByteArrayOutputStream();
WorkbookSettings settings = new WorkbookSettings();
settings.setWriteAccess(null);
WritableWorkbook workbook = Workbook.createWorkbook(os, template, settings);
WritableSheet sheet = workbook.getSheet(0);
WritableCell cell = sheet.getWritableCell(1, 2);
if (cell.getType() == CellType.LABEL) {
  Label l = (Label) cell;
  l.setString("modified cell");
}
workbook.write();
workbook.close();
実行してみると、めでたくsample.xlsがコピーされたExcelファイルがダウンロードできた。\(^o^)/

後は日本語の扱いが気になるのでもう少し調べてみる必要があるな。

2010-06-19

GAE/J + Slim3 : メール受信アプリを作ってみる(4)

今回はMailServiceを作成する。
前回作成したテスト MailServiceTest#receive_plainText_UTF8()をパスするように MailService#receive()を実装する。
◎まずはコンパイルエラーをなくすように必要最低限のコードを書いてみる。
public MailItem receive(String pAddress, InputStream pInputStream) {
    MailItem mailItem = null;
    return mailItem;
}
これで、コンパイルは通るのでテストを実行してみる。
結果は...NullPointerExceptionでRed、receive()の戻り値がnullなので当たり前だな。
◎次はテストをパスするようにコードを書く。
public MailItem receive(String pAddress, InputStream pInputStream) {
    MailItem mailItem = null;
    Properties prop = new Properties();
    Session session = Session.getDefaultInstance(prop, null);

    try {
        MimeMessage message = new MimeMessage(session, pInputStream);
        mailItem = new MailItem();
        mailItem.setFrom(message.getFrom()[0].toString());
        mailItem.setTo(message.getRecipients(RecipientType.TO)[0].toString());
        mailItem.setSubject(message.getSubject());
        InputStream in = (InputStream)message.getInputStream();
        Reader reader = new InputStreamReader(in);
        BufferedReader bufReader = new BufferedReader(reader);
        StringBuilder sb = new StringBuilder();
        String line;
        while ((line = bufReader.readLine()) != null) {
            sb.append(line + "\n");
        }
        mailItem.setBody(sb.toString());
        Datastore.put(mailItem);
    } catch (MessagingException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
    return mailItem;
}
◎コンパイルは通っているので、テストを実行してみる。もちろんGreen、これで基本パターンはOK (^_^)v
次回は通常のメールと同じように文字コードが"ISO-2022-JP"の場合を考えてみる。

2010-06-17

GAE/J + Slim3 : メール受信アプリを作ってみる(3)

今回はMailServiceTestを作成する。
まずは受信メールを表すモデルクラス MailItemを作成しておく。
  • メール受信のサービス: "gen-model"で /src/../model/MailItem.java と /test/../model/MailItem.java を生成する。
  • 属性はfrom, to, subject, bodyを作成する。型は全てStringとする。

では、MailService#receive(String pAddress, InputStream pInputStream)のテストを作成する。
文字コードは、現時点では余計なことは考えたくないので"UTF-8"としておく。またマルチパートも現時点では考慮しない。
最初は、単純で基本的な処理から考えていくのが"吉"です。(^_^)

テストメソッドは以下のようになった。
public class MailServiceTest extends AppEngineTestCase {
    private MailService service = new MailService();

    /**
     * シングルパート(text/plain)を入力し{@link MailItem}オブジェクトが生成されること
     * @throws UnsupportedEncodingException
     */
    @Test
    public void receive_plainText_UTF8() throws UnsupportedEncodingException {
        String mailFormat = "Received: by 10.115.25.19 with SMTP id c19mr2897164waj.34.1275464064977;\n"
            + "Wed, 02 Jun 2010 00:12:34 -0700 (PDT)\n"
            + "Return-Path: <%1$s>\n"
            + "Received: from smtp.hoge.org (ml.hoge.org [128.1.2.3])\n"
            + "by gmr-mx.google.com with ESMTP id u10si11332059wak.6.2010.06.02.00.34.24;\n"
            + "Wed, 02 Jun 2010 00:12:34 -0700 (PDT)\n"
            + "Received-SPF: pass (google.com: best guess record for domain of sender@hoge.org designates 128.1.2.3 as permitted sender) client-ip=128.1.2.3;\n"
            + "Authentication-Results: gmr-mx.google.com; spf=pass (google.com: best guess record for domain of sender@hoge.org designates 128.1.2.3 as permitted sender) smtp.mail=sender@hoge.org\n"
            + "Received: from [128.1.2.3] (unknown [128.1.2.3])\n"
            + "by smtp.hoge.org (Postfix) with ESMTPA id 8F1C716838E\n"
            + "for <%2$s>; Wed,  2 Jun 2010 00:12:34 -0700 (PDT)\n"
            + "Message-ID: <4C060983.1090109@hoge.org>\n"
            + "Date: Wed, 02 Jun 2010 00:12:34 -0700\n"
            + "From: SENDER <%1$s>\n"
            + "User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.1; ja; rv:1.9.1.9) Gecko/20100317 Thunderbird/3.0.4\n"
            + "MIME-Version: 1.0\n"
            + "To: RECEIVER <%2$s>\n"
            + "Subject: %3$s\n"
            + "Content-Type: text/plain; charset=UTF-8\n"
            + "\n"
            + "%4$s\n";
        String encodedSubject = MimeUtility.encodeText("これは件名", "UTF-8", "B");
        String mailStr = String.format(mailFormat, "sender@hoge.org", "reciever@fuga.org", encodedSubject, "本文1行目\n本文2行目");

        byte[] bytes = mailStr.getBytes();
        InputStream in = new ByteArrayInputStream(bytes);
        MailItem mailItem = service.receive("sender@hoge.org", in);
        assertThat(mailItem.getFrom(), is("SENDER <sender@hoge.org>"));
        assertThat(mailItem.getTo(), is("RECEIVER <reciever@fuga.org>"));
        assertThat(mailItem.getBody(), is("本文1行目\n本文2行目\n"));
        assertThat(mailItem.getSubject(), is("これは件名"));
    }
}
内容は、
  • encodedSubjectに、件名を"UTF-8"でエンコードした文字列をセットする(=?UTF-8?B?44GT44KM44Gv5Lu25ZCN?= となる)
  • mailStrにヘッダーを含めた受信メールの文字列を作成する
  • 次の2行でこのmailStrをInputStramに変換する
    byte[] bytes = mailStr.getBytes();
    InputStream in = new ByteArrayInputStream(bytes);
    
  • MailService#receive()メソッドを実行する
  • 生成されたmailItemの内容を検証する
となっている。

この時点ではコンパイルエラー(MailService#receive()が未定義)となっているはず。
次回はこのテストをパスするようにMailService#receive()を実装していく。

2010-06-10

GAE/J + Slim3 : メール受信アプリを作ってみる(2)

仕事で他プロジェクトの応援に借り出されて忙しかったので間が空いてしまった。(-_-;

前回、メール受信ができるように設定を行ったので、メール受信するプログラムを作っていく。
まずは必要となるソースを生成しておく。とりあえずコントローラとサービスは必要なので、これらを生成しておく。
  • メール受信のコントローラ: "gen-controller"で /src/../controller/mail/ReceiveController.java と /test/../controller/mail/ReceiveControllerTest.java を生成する。
  • メール受信のサービス: "gen-service"で /src/../service/MailService.java と /test/../service/MailServiceTest.java を生成する。

さて最初はMailServiceのテストだが、仕様を考えなければいけないのでGAE/Jでのメール受信について調べてみる。
メール受信に関しては「Google App Engineドキュメント」の[Java]-[Services]-[Mail]-[Receiving Email]に書かれている。
ここの「Handling Incoming Email」を読むと下記のコードでMimeMessageオブジェクトが取得できる。
import java.util.Properties;
import javax.mail.Session;
import javax.mail.internet.MimeMessage;

Properties props = new Properties();
Session session = Session.getDefaultInstance(props, null);
MimeMessage message = new MimeMessage(session, req.getInputStream());

ということで、メール受信のサービス MailServiceの仕様は次のようにする
[receiveメソッド]
  • 引数
    • 受信メールアドレス: コントローラに渡される受信メールアドレス
    • InputStream:メールの内容(上記コード例のreq.getInputStream())
  • 処理内容
    • 引数 InputStreamからMimeMessageを生成する
    • MimeMessageから"From", "To", "件名", "本文"を取り出す
    • 取り出した項目から受信メールを表すモデルクラス(MailItem)を生成する
    • 生成したMailItemを戻り値として返す

風邪気味なので今日はここまで、次回はMailServiceTestを作成する。

2010-06-03

GAE/J + Slim3 : メール受信アプリを作ってみる(1)

会社で開発しているGAE/J + Slim3アプリの開発作業がようやく起動に乗ってきたので、"個人的興味"で"個人的"なアプリを作ってみる。
単なるWebアプリではつまらないので、メールで処理依頼を受け付けられるようにしてみる。
ということで、メールを受信するアプリをGAE/J + Slim3で作ってみる。

まずは設定関係から...
この設定を行えば、アプリ宛のメール"abc@xyz.appspotmail.com"が受けられるようになる。
[war/WEB-INF/appengine-web.xml]
<inbound-services>
    <service>mail</service>
</inbound-services>

[war/WEB-INF/web.xml]
<security-constraint>
    <web-resource-collection>
        <url-pattern>/_ah/mail/*</url-pattern>
    </web-resource-collection>
    <auth-constraint>
        <role-name>admin</role-name>
    </auth-constraint>
</security-constraint>

受信したメールをどのようにアプリで受け取るかというと、URL "/_ah/mail/メールアドレス"にマッピングされるのでサーブレットで処理すればよい。
Slim3の場合、AppRouter.javaでメール受信処理を行うコントローラのURLへのルーティングを定義すればよい。(Slim3 Controller - URL mapping参照)
[controller/AppRouter.java]
public AppRouter() {
  addRouting("/_ah/mail/{address}", "/mail/receive?address={address}");
}
このコードでは、メールを受信すると /mail/receiveにリクエストがくることになる。またメールの宛先(To)アドレスはリクエストパラメータ"address"として渡されてくる。

これでメールを受信する準備が整ったのでメール受信用のコントローラを作ってみる。もちろん、テストもネ!

2010-03-13

GAE/J + Slim3 [はじめてのSlim3(5)]

今回でSlime3公式サイトの「Getting Started」チュートリアルは最後、「Listing tweets」で入力したtweetが一覧表示されるようになる。

  • まずはMVCモデルのViewの部分となる war/twitter/index.jspを修正する。<form>~</form>の後に下記コードを追加する。
    ここでやっていることは
    1. "forEach"でtweetListから要素を一つづつ取り出す
    2. その要素をh()でエンコードして表示
    3. <hr />で水平線を表示
    で、この部分に一覧表示されることになる。
    <c:forEach var="e" items="${tweetList}">
    ${f:h(e.content)}
    <hr />
    </c:forEach>
  • では http://localhost:8888/twitter/ にアクセスして表示してみる。
    ...
    一覧に何も表示されない、これはJSPで表示しようとしている"tweetList"が作成されていないからだ。
  • war/twitter/index.jspに対応するのはcontroller.twitter.IndexContorollerとなるので、このクラスで"tweetList"を作成することになる。
    ということで、まずはテスト(仕様)を書く。controller.twitter.IndexContorollerTest#run()の最後に下記を追加する。
    assertThat(tester.requestScope("tweetList"), is(notNullValue()));
  • それじゃ、テスト実行(もう慣れたかな?リズムに乗ろう!)
    結果は...Red(NG) なぜかというと、まだIndexControllerに"tweetList"を作成するコードを書いていないから、ということでこの段階では"正常"。
  • では、IndexControllerを修正しよう。
    ここで一つTips、eclipseに「moreUnit」プラグインがインストールされていれば"Ctrl+J"でテストクラスと対象クラスを行き来できるようになるので切り替えが楽チンです。
    修正後は下記のようになる。
    ここでやることは
    1. TwitterService#getTweetList()でTweetのリストを取得する。
    2. それをrequestScope("tweetList", tweetList)でアトリビュート"tweetList"にセットする。
    public class IndexController extends Controller {
        private TwitterService service = new TwitterService();
        @Override
        public Navigation run() throws Exception {
            List<Tweet> tweetList = service.getTweetList();
            requestScope("tweetList", tweetList);
            return forward("index.jsp");
        }
    }
  • ここでgetTweetList()が未定義というエラーが出る。そう、TwitterService#getTweetList()はまだ存在していない。getTweetList()を作ることになるのでが、その前にテストを作る(「テスト駆動」だね(^_-))
    TwitterServiceTestに下記のテストメソッドを追加する。ここで"getTweetList()"がエラーとなっているはず。
    @Test
    public void getTweetList() throws Exception {
        Tweet tweet = new Tweet();
        tweet.setContent("Hello");
        Datastore.put(tweet);
        List<Tweet> tweetList = service.getTweetList();
        assertThat(tweetList.size(), is(1));
        assertThat(tweetList.get(0).getContent(), is("Hello"));
    }
  • では続いてビルドエラーをなくすためにTwitterService#getTweetList()メソッドを作る。エラーの出ている箇所にカーソルを合わせて"Ctrl+1"を押す。
    "型TwitterServiceのgetTweetListメソッドを作成します"を選ぶ。
  • するとTwitterServiceのソースが開いてgetTweetListメソッドの雛形ができているので下記のように修正する。
    フィールド t を定義
    private TweetMeta t = new TweetMeta();
    getTweetListメソッドを次のように修正する
    public List<Tweet> getTweetList() {
        return Datastore.query(t).sort(t.createdDate.desc).asList();
    }
  • さあ、これでコンパイルエラーがなくなったので、テストを実行する。
    ...
    結果はGreen(OK)
    全てのテストを実行していない場合はこれが最後なので実行しておこう。
    全テストを実行してもさほど時間はかからないので、必ず全テストを実行するようにしよう。(ちょっとした修正が思わぬところに影響を及ぼすこともあるので)
  • テストがAll Green(OK)となっていることを確認して http://localhost:8888/twitter/ にアクセスして動きを確認してみよう。
    何か文字列を入力して[tweet]ボタンをクリックすると一覧が表示される。
  • GAEにデプロイしてアクセスしてみよう。ローカルと同じように動くのが確認できる。うーん、簡単すぎる(^_^;
  • これでTutorialが終了した。
    ここで、一通り終えてみての感想を...
    • 全般的に、サクサクとストレスなく進められて気持ちイイ
    • Slim3 DatastoreのAPIが「S2JDBC」のような"流れるようなインタフェース"でスラスラ書けて読みやすい
    • 「テスト駆動(TDD)」は、普段の開発(主に「Teeda」)で実践しているので、特に違和感なく進められる。
      未経験の人はぜひ実践を。「アジャイルプラクティス 第5章 作る前から使う」に書かれているがシンプルな設計になり易い
開発(特に仕事の場合)は、様々なストレス要因があってプログラミングに集中できないことが多い。集中できないとミスをおこす可能性も高くなるので、余計な事に気を使わずにテンポよくプログラミングが進められる「Slim3」は気に入りました。
 今度、会社でGAE/J上に構築する予定のシステムがあるので「Slim3」を使ってみようかと思います。
個人でもアカウントは作ってあるので家族で使うようなアプリを作ってみようかな。

2010-03-12

GAE/J + Slim3 [はじめてのSlim3(4)]

一日、間が空いてしまいました。m(_ _)m
隣の部署で大火事状態のプロジェクトがありヘルプにかり出されたので、更新する時間が取れなかった(;_;)

前回の続き、「Creating a form」の最後のところ...TwitterService#tweet()メソッドを作成してテストがGreen(OK)となった続きから。
  • 次に進む前にTwitterService#tweet()メソッドで何をしているのか確認してみる。
    public Tweet tweet(Map<String, Object> input) {
        Tweet tweet = new Tweet();
        BeanUtil.copy(input, tweet);
        Transaction tx = Datastore.beginTransaction();
        Datastore.put(tweet);
        tx.commit();
        return tweet;
    }
    • BeanUtil.copy()でTweetオブジェクトに入力値を設定している。
      このcopy()メソッドが便利で第一引数(Mapオブジェクト)のKey値と同じ名前の"第二引数のプロパティ"にValue値をセットしてくれる。(contentに値が設定されることになる)普通は一つ一つgetter()メソッドを使ってセットするコードを書くのだが、こういう単純作業は人がやるべき作業じゃないよね。
    • Datastore.put(tweet)でTweetオブジェクトをデータストアに格納している。
      App Engineデータストアを扱う場合「Datastore Java API」で説明されているように、JDO, JPA, 低レベりAPIのいずれかを使用することになるが、ここではSlim3のDatastoreを使っている。これはタイプセーフな低レベルAPIのラッパーということでJDO,JPAを使うより早いらしい。
  • では、続き...
    今作ったTwitterService#tweet()メソッドをTweetControllerから呼んで、入力された"tweet(つぶやき)"をデータストアに格納されるようにする。
    ということで、この仕様をTweetControllerTestに書く。既にテストメソッドrun()は作成されているので下記のように修正する。
    @Test
    public void run() throws Exception {
        tester.param("content", "Hello");
        tester.start("/twitter/tweet");
        TweetController controller = tester.getController();
        assertThat(controller, is(notNullValue()));
        assertThat(tester.isRedirect(), is(true));
        assertThat(tester.getDestinationPath(), is("/twitter/"));
        Tweet stored = Datastore.query(Tweet.class).asSingle();
        assertThat(stored, is(notNullValue()));
        assertThat(stored.getContent(), is("Hello"));
    }
  • テストクラスを修正したので、テスト実行...Red(NG)
    TweetControllerはまだ修正していないので、今の時点ではRed(NG)で"正常"
  • では、テストが通りようにTweetControllerを修正する。"return redirect(basePath)"の前に一行追加する。
    service.tweet(new RequestMap(request));
  • テストを実行して...はい、Green(OK)
  • では実際に動かしてみる。「GAE/J + Slim3 [準備]」で作成しておいた"Webアプリケーション"が起動されていることを確認して、ブラウザから http://localhost:8888/twitter/ にアクセスする。
  • テキスト欄に何か文字を入力して[tweet]ボタンをクリックする。何も変化はないけど、入力した文字列がデータストアに格納されているはず。
  • データストアの内容を確認するにはブラウザで http://localhost:8888/_ah/admin/ にアクセスする。これはローカル開発用のコンソールでデータストアやTask Queueの内容が確認できる。
  • "Datastore Viewer"をクリックして、[List Entries]をクリックすると、データストアに格納されているデータの内容が表示される。ここに先ほど入力した文字列のデータが表示されているはずだ。
あと残りは入力したtweetを一覧表示する処理をつくる「Listing tweets」だ、今度の土日でやろう。p(^^)g

2010-03-10

GAE/J + Slim3 [はじめてのSlim3(3)]

前回の続き、「Creating a form」の途中から...TweetControllerクラスのrun()メソッドを修正してテストがGreen(OK)となった続きから。
  • モデルクラスのTweetクラスを生成する。build.xmlのgen-modelタスクを実行し、モデル名に"Tweet"と入力すると以下のソースが作成される。
    • src/(ルートパッケージ)/model/Tweet.java
    • test/(ルートパッケージ)/model/TweetTest.java
  • 新しくソースを生成したのでテストを実行する...はい、Green(OK)(^_^)
  • この時点で、Tweetクラスはデータストアに格納するための必要最低限の属性(key, version, schemaVersion)しか持っていない。
    このクラスは"/twitter/index.jsp"から入力された「tweet(つぶやき)」を表すクラスなので、それに必要な属性(content, createdDate)を追加する。以下のフィールドを追加してgetter,setterも追加する。
    private String content;
    private Date createdDate = new Date();
  • ソースを修正したのでテスト実行...はい、Green(OK)だね(^_^)
    ここでテストクラスの作り方について...元サイトにも書かれていることだが、教科書的にテストクラスを作るとgetter, setter一つ一つにテストメソッドを作ることになるんだが、実際の開発現場ではそんな事してられない。(-_-)
    というのは、Tweetクラスのソースを見てみるとgetter, setterは、フィールドをそのまま返す、設定するとしているだけであり、問題が起こる可能性はまずない。このようなgetter, setterのテストメソッドをイチイチ作っていたら時間がいくらあっても足らない。
    ということで、実際の開発現場ではgetter, setterのテストに関しては何らかのデータ加工を行っている場合だけ作っています。...閑話休題
  • 次はサービスクラスTwitterServiceを作成する。このクラスはモデルのTweetクラスをデータストアに格納する役目を担うことになる。
    では、build.xmlのgen-serviceタスクを実行し、サービス名に"TwitterService"と入力すると以下のソースが作成される。
    • src/(ルートパッケージ)/service/TweetService.java
    • test/(ルートパッケージ)/service/TweetServiceTest.java
  • 新しくソースを生成したのでテストを実行する...今回もGreen(OK)
    ここまで何回かテストを実行してきたけど、Red(NG)となったのは確か一回だけ。Green(OK)となるのが分かっているのに何でイチイチ実行しなければならないんだ、と思うよね。たしかにTweetServiceのソースは中身空っぽだし、TweetServiceTestではnotNullValue()[nullでない]のテストなのでGreen(OK)になるのは当たり前だよね。
    でも、ここで大事なのは「テスト作成」-「テスト実行」-「ソース修正」-「テスト実行」のサイクルを回して"リズム"に乗ること。さあ、リズムに乗って次に進もう。
  • 次はTweetService#tweet()メソッドのテストを作る。テストメソッドは下記のとおり。
    @Test
    public void tweet() throws Exception {
        Map<string, object=""> input = new HashMap<string, object="">();
        input.put("content", "Hello");
        Tweet tweeted = service.tweet(input);
        assertThat(tweeted, is(notNullValue()));
        Tweet stored = Datastore.get(Tweet.class, tweeted.getKey());
        assertThat(stored.getContent(), is("Hello"));
    }
    はい、ここで「あれ?」と気づいた人(^_^)/
    ……
    そう、TweetService#tweet()メソッドはまだ存在していない、eclipseのエディタで"tweet"のところに赤い波線がついていて「メソッド tweet(Map) は型 TwitterService で未定義です」とコンパイルエラーになっている。
    これが「テスト駆動」!
    テスト=仕様を書く→テストが通る(仕様を満足する)コードを書く、という具合にテストによって駆動される。
  • では、TweetService#tweet()メソッドを実装しよう。
    eclipseでは簡単、
    • エラーの出てる箇所("tweet")にカーソルを合わせて"Ctrl+1"を押す
      Tweet tweeted = service.tweet(input);
    • ポップアップから"型 'TwitterService'のメソッド 'tweet(Map)'を作成します"を選択する。
    • TweetService#tweet(Map)メソッドが作成される
  • これでコンパイルエラーが無くなったので、テスト実行...Red(NG)(-_-)
    tweet()メソッドはeclipseが自動生成して
    "return null"
    となっているので
    "assertThat(tweeted, is(notNullValue()));"
    でNG、ということでこの時点では正常。(テストが通らないことを確認することも大事)
  • それでは、テストを通すようにtweet()メソッドを実装する。
    public Tweet tweet(Map input) {
        Tweet tweet = new Tweet();
        BeanUtil.copy(input, tweet);
        Transaction tx = Datastore.beginTransaction();
        Datastore.put(tweet);
        tx.commit();
        return tweet;
    }
    
  • では、テスト実行...Green(OK)
あともう少しなんだが、日付が変わったしまったので今日はここまで。
m(_ _)m

※追記[2010.3.10 22:00] ソースコード部分を見やすくしました。(ソースコードHTMLコンバーターを使わせて頂きました)

2010-03-09

GAE/J + Slim3 [はじめてのSlim3(2)]

前回の続きで「Creating a form
  • war/twitter/index.jspのbodyタグ内を下記のように編集する
    <p>What are you doing?</p>
    <form method="post" action="tweet">
    <textarea name="content"></textarea><br />
    <input type="submit" value="tweet"/>
    </form>
  • 続いてこのフォームをsubmitした際の処理を行うTweetControllerを生成する。build.xmlのgen-controller-without-viewタスクで"/twitter/tweet"と入力すると以下のソースファイルが生成される("without-view"なのでjspは生成されない)
    • src/(ルートパッケージ)/controller/twitter/TweetController.java
    • test/(ルートパッケージ)/controller/twitter/TweetControllerTest
  • テストを実行してGreen(OK)となることを確認する
  • 次にテストクラス:TweetControllerTestを修正する。TweetControllerクラスは/twitter/tweet からリクエストを受けて/twitter/にリダイレクトすることになるので、それをテストするように修正する。
    run()テストメソッドの最後の2行を下記のように修正する。
    assertThat(tester.isRedirect(), is(true));
    assertThat(tester.getDestinationPath(), is("/twitter/"));
  • ここからが「テスト駆動開発」の面白いところ。(^_^)/
    テストを実行してRed(NG)になることを確認する。
    これはまず対象クラスのテスト=仕様をコードで記述すること、そしてそのテストが失敗となることを確認することである。
    この時点でテストがOKとなってしまったら変だよネ。だから"正しく"失敗することを確認しなければならない。
  • では、次にテストが通るようにTweetControllerのreturn文を下記のように修正する。これでbasePath="/twitter"にリダイレクトされるようになる。
    return redirect(basePath);
    
  • これでテストが通るはず。テスト実行...よし!Green(OK)だ(^_^)v
このセクションは少し長いので、今日はここまで。

ついでに「テスト駆動」について。「テスト駆動」では基本的に下記手順で開発を進める。
  1. テストを書く
  2. テストを実行して結果(通常失敗(Red))を確認する
  3. コードを修正する
  4. テストを実行して成功(Green)することを確認する
  5. リファクタリングする
  6. テストを実行して成功(Green)することを確認する
実際にソフト開発作業で「テスト駆動」で行っているが、旧来の開発作業と比べて一番のメリットは、精神的に楽に開発できること。(^_^)
 旧来のやり方だとテストを始めるのは、一クラス全部コーディングしてから、または複数のクラスをコーディングしていきなり結合レベルのテストをしていた。
仕様通りに動作するかどうか分からない状態のまま(精神的にはものすごく不安な状態)黙々とコーディングし続け、いきなり単体テスト/結合テストを行う。こんなの動くわけないでしょ(-_-
それに精神的に不安で高ストレスの状態でコーディングしていたらミスを起こす可能性も高くなる。
 テスト駆動では、まずテスト(javaの場合はJUnit)を書く。"テスト"というよりは"仕様"をコードで表すと言った方がピッタリくるかな。このテストもいきなりたくさん書くのではなくて、テスト対象メソッド一つについてのテストを書く。(テスト要因が多い場合には、まず正常系、次に異常系という具合に進めていくと整理しやすい)
このように少しづつ進めることで一歩一歩確実に前に進めるので、精神的に非常に安定した状態で開発ができる。それと[テストを書く]-[テスト実施]-[コードを書く]-[テスト実施]-[リファクタリング]というサイクルを繰り返すことでリズムが出てきて、「ここまでOK」、「次...ここまでOK」という感じでスイスイ前に進み感覚になり、楽しくなる。
もう一つのメリットは「回帰テスト」が楽にできること。テストプログラムなので何十項目のテストも実行すれば一瞬で完了する。なので区切りのついた時やコミットする前などには必ずすべてのテストを実施してレベルダウン(デグレード)が無いことを確認するようにしている。

 ユニットテストの効用については2年位前に買ったお気に入りの本「アジャイルプラクティス 達人プログラマに学ぶ現場開発者の習慣」の[5章 アジャイルなフィードバック]に色々説明されている。
この本、行き詰まった時などに読み返して[アジャイル]の初心にかえるようにしている。もう何回読んだかな?(結構影響受けたので、いずれこの本について書こうかな)
開発者なら「達人プログラマー―システム開発の職人から名匠への道」と並んで必読の本なので、まだ読んでない人はぜひ読んでくださいナ。
(↑Amazonの"商品プレビュー"を設定してみた、書籍名にマウスカーソルを合わせると本の画像がポップアップ表示されますよ)

2010-03-07

GAE/J + Slim3 [はじめてのSlim3(1)]

Slim3でアプリを作ってみる。
Slim3「Getting Started」-「Creating a controller and a test」に沿って進める。
  • プロジェクトの名前を"slim3-blank"から変更する
  • (プロジェクトroot)/war/WEB-INF/web.xmlの下記箇所の"param-value"の値をルートパッケージ名に変更する
    <context-param>
        <param-name>slim3.rootPackage</param-name>
        <param-value>tutorial</param-value>
    </context-param>
  • build.xmlの"gen-controller"タスクを実行する
  • コントローラのパス入力のダイアログが表示されるので手順通り"/twitter/"と入力する
  • すると以下のファイルが生成される(コントローラクラスとそのテストクラス、それとJSP)
    • src/(ルートパッケージ)/controller/twitter/IndexController.java
    • test/(ルートパッケージ)/controller/twitter/IndexControllerTest.java
    • war/twitter/index.jsp
  • "Webアプリケーション"が起動していなかったら起動する
  • ブラウザで http://localhost:8888/twitter/ にアクセスする
  • Hello twitter Index !!! と表示されればOK
  • テストクラスもあるのでテストを実行してみると...もちろん、Green(OK)
ここまで「あっと言う間」、テストクラスも含めて"サクっ"と作れてしまうのは凄く楽チン(^_^)
余計なことに惑わされずに中身に集中できそうだ。

仕事で「Teeda」を使った開発をしているが、これと同じ感覚で"サクサク"と進められそうだ。

2008-04-06

ミニバス スコア記録アプリ(2)


ここの所、会社の新人研修の準備だとか会社で使っているPCのレンタル切れによる資材の引越し作業とかあって、忙しくてなかなか時間がとれなくてblogの更新が疎かになってしまってます。m(._.)m

今日はスコア記録アプリのスコア入力の画面紹介です。

一番上が左からクォータ、得点、タイマーでタイマー部分はタップすることでタイマーのスタート/ストップが切り替わります。
4~8の番号は出場選手の番号で[交代]ボタンをクリックすると選手交代ができます。
ファウル欄は上からチームファウル数、各選手のファウル数で選手ファウル数部分はボタンになっていて、ここをクリックしてファウルのカウントを行います。また[取消]ボタンをクリックすると直前にカウントしたファウルを取り消します。
得点欄は[St]がシュート、[FT]がフリースローをカウントするボタンで対応する選手の得点、クォータ得点、チーム得点がカウントアップされます。[取消]ボタンをクリックすると直前に入力した得点を取り消します。
チームの切り替えはタブで行います。

とりあえず、ここまで動くようになりました。今はCSVファイルへの出力機能を作成中なんだけど、冒頭に書いたように中々時間がとれなくて進んでいません。(-_-;
来週、試合があるので実際に使ってみて操作性とかみてみようと思ってます。
...が、子供たちの面倒でそれどころじゃないかも。

2008-03-24

ミニバス スコア記録アプリ(1)

「Windows Mobileアプリを作ってみる」で作っていた「ミニバス スコア記録アプリ」が動くようになったので、その紹介です。CSVファイル出力はまだできていないので、今の時点では使い物にならないんだけど...(^^ゞ
とりあえず今回は画面の説明を。(スコア記録画面の画像を撮っていなかったので、チーム登録と試合登録の画面まで m(__)m)
















1)まずはメイン画面、起動するとこの画面が表示されます。ここでは"大会名"、"会場"、"開催日"を入力します。チーム・タブでは登録されているチームの一覧、試合タブでは登録されている試合一覧が表示されます。

2)次にチームの登録は[メニュー]-[チーム追加]で行います。
3)チーム登録の画面では"チーム名"、"選手番号最大値"、"選手名"を入力します。"選手名"の入力欄は"選手番号最大値"で選択した数値により増減します。また"選手名"の初期値は"No.x"で任意の文字列に変更可能です。
3)試合の登録は[メニュー]-[試合情報追加]で行います。メイン画面の試合一覧でチーム名の横の括弧内の数字は得点で、スコア入力されていれば表示されます。
4)試合登録の画面では、登録チームから対戦チームを選択します。このドロップダウンリストボックスには登録されているチームが表示されます。同一チームは選択不可です。

2008-03-09

Windows Mobileアプリを作ってみる(5)

モデル部分の再設計、2日で終わった。実質的には4~5時間程度かな。一度ある程度形を作ったのを整理して再構成したということもあって、以外と短時間でできた。もちろんテストクラスも作ってある。
今週は、これにGUIを被せていく。これもある程度できているのであまり時間は掛からないかな?
最終的には記録したスコアをCSVファイルに出力できるようにする予定だ。

2008-03-05

Windows Mobileアプリを作ってみる(4)

ここのところ忙しくて(休日出勤もあったし)、随分間が空いてしまった。(^^ゞ
ミニバスのスコア記録アプリだが、選手交代までは動いた。
だが画面操作側を主体に機能を追加していったので、ドメインモデル(ロジック部分)のクラス設計がよくなく融通が利かないようになってしまった。
ということでドメインモデルのクラス設計をやり直すことにした。遊び半分にWindowsMobileアプリの調査も兼ねて作り始めたので、適当に作ってしまった部分もあり、そのツケが回ってきたんだな。
どんなアプリでも設計はしっかりとやっておくべきだな。 \(_ _ )反省
今月中には一通りの機能を実装して実際にスコアが記録できるところまで作りたいな。(細かい操作性までは手が回らないだろうけど)

2008-02-09

Windows Mobileアプリを作ってみる(3)

ミニバスのスコア記録アプリ、少しづつ形が出来てきた。
今動いているのは次の機能。
  • 大会情報の入力(大会名、日付、会場名)
  • 試合情報の入力(チーム名A、チーム名B)
  • クォータの指定
  • ファウルのカウント(とりあえずカウントするだけ)とカウント取消し
  • 得点のカウントと取消し(シュート、フリースローを選手ごとにカウント、クォータ単位に集計)
  • タイマー(6分計)のスタート、ストップ
  • 選手交代の指定(交代指定の画面だけ(^^ゞ)
後はファウル、得点の履歴表示とこれらの機能をまとめること。
それと画面デザインだな、今は適当に並べているだけなので...
ここの所、本業が忙しく時間が割けないのであまり進んでいない。まあ少しづつ進めるしかないな。

2008-01-31

Windows Mobileアプリを作ってみる(2)

この前作り始めたWindows Mobileアプリが少しづつ動くようになってきた。
今はロジック部分を主に作っていてGUI部分がまだまだなので公表するのはもうちょっと先です。
ちなみにどんなアプリかというと、ミニバスケットのスコア記録を行うアプリです。(ミニバスは、野球とかサッカーに比べるとマイナーなので"使いたい"っていう人いるかな?)
娘がスポ少でミニバスをやっているので、勉強と実益を兼ねて作っています。

Windowsアプリとの違いで戸惑ったことをメモしておく。
  • フォームの[X]ボタンでフォームがクローズしない。(最小化される?)
  • フォームは常に最大化状態:デザイン時に小さいサイズで作成しても、実行時に表示すると最大化状態(画面フルサイズ)で表示される。
.NET Compact Framework 2.0で作っているが、面白いことがあった。
ちょっとした好奇心で、ビルドしてできた.EXEをWindows上(Windows XP)で実行してみた所、ちゃんと動いちゃいました。(^^;
ということは、デバイスエミュレータが無くても動作確認はできるってこと?
モバイルデバイスでは下側に表示されているメニュー、タブコントロールのタブが、Windows上では上部に表示されるという違いがあるが、それ以外はそのまま同じように表示されます。
考えてみれば当たり前で、.NET Framework用のコードが出力されていて、.NET Framework上で動かしているので、動かないわけがない。
Javaの"Write Once, Run Anywhere"と同じですな。(^_^)
そう考えると、このJavaの発想はすごいよなぁ。
そういえばJavaの分散システム関連で「Jini」という面白そうなのがあったけど、今どうなっているのかな?...で、調べてみたら(→Wikipedia:Jini)、「Apache River Project」になっていました。
今度調べてみようかな。

2008-01-28

今までに扱ったことのある言語

学生の頃から現在まで長年ソフト開発を行ってきていて、それに伴ってプログラミング言語もたくさん扱ってきているので、それを書き出してみる。
こうして書き出してみると結構やってるなぁ(^^ゞ
ここに挙げた言語で、一番好きなのは"Java"だな。今Webアプリの開発がメインになっているのもあるけど、何か"性に合ってる"んだよな。それと新しい技術(ちょっと前だとAOPとかDI)が早く取り入れられるので面白くて楽しいんだよね。
ということで暫くは"Java"での開発を続けます。RubyとかPythonに浮気するかもしれないけど(^^ゞ

【学生時代】
"TL/1"までは自分のPC-8001で、それ以降は大学で扱った。(これ見ると年齢分かりそう...(^_^;)
APLは面白かったなぁ(*^_^*) 配列処理が得意な言語で、如何に少ない行数でプログラミングするかに燃えていたな、究極は1行(いわゆるワンライナー)で組み上げることで楽しかったなぁ
  1. BASIC(N-BASIC)
  2. Z80アセンブラ
  3. Tiny PASCAL
  4. TL/1
  5. PDP-11アセンブラ
  6. FORTRAN
  7. PASCAL
  8. PL/I
  9. APL

【就職後】
"68000アセンブラ"までは趣味で、"PL/I"以降は仕事で扱った。この内"X-BASIC"から"68000アセンブラ"まではあの"X68000"で使った言語で、このマシンは今まで一番使い込んで一番好きなマシンで今は押入れに眠ってる。まだ動くかな?久しぶりに電源入れてみようかな。
  1. N88-BASIC
  2. 8086アセンブラ
  3. X-BASIC
  4. GNU C
  5. 68000アセンブラ
  6. PL/I
  7. COBOL
  8. FACOM Mシリーズアセンブラ
  9. Microsoft C
  10. PowerBuilder
  11. Delphi
  12. Visual Basic
  13. Visual C++
  14. Java/JSP/Servlet
  15. PHP
  16. Perl
  17. JavaScript
  18. Ruby
  19. VB.NET
  20. C#

2008-01-25

Windows Mobileアプリを作ってみる

Windowsアプリとの違いがどんなものかWindows Mobileのアプリを作り始めてみた。
.NET(C#)で作っているので、今のところそれほど大きな違いはない。これは.NETのありがたみだな(^.^)
違うのは、コントロールのプロパティが無かったり、選択できる値が少なかったりといった点で、それほど違和感なくプログラミングできている。
X01Tにコピーして動かしてみたが特に問題なく動いた。(まだ大した事やっていないけど(^^; )
もうちょっと形ができてきたらここで紹介しようかな?

ところでWindows Mobile上でプログラミングできるプログラミング言語ってないかな?
探してみよう。