Tag Archives: Javascript

JavaScript Advent Calendar 2011 (フレームワークコース) 21日目の記事です。

Sencha Touchを初めて使った時の話で、あまり実用的な内容ではないですが、
これからSencha Touchを使う人に何かしら参考になればと思います。

Sench TouchでiPadモック

Sencha Touchとは…という話は恐らく聞いたことがある人は多いと思うので省きますが、
iOSとAndroid(要はWebkit系)をサポートしたスマートフォン向けのWebアプリケーションフレームワークです。

最近はObjective-CでiPhoneアプリを書いている事が多いのですが、
今回、問診票iPadアプリを作成する事になり、それ向けに動くモックが一週間後ぐらいに必要となりました。
まだiPad向けのアプリは作成した事はなかったので時間的な心配と、 Sencha Touch ちょっと触ってみた所感などを読んでSencha Touchが気になっていたので、
Sencha Touchでモックを作って見ることにしました。

Sencha Touchを初めて3日ほどかかって、かろうじて動く感じのモックができました。

  • http://azu.github.com/SenchTouchiPadMock/www/
    Webkit系のブラウザならiOSじゃなくても見られます
    全然Sencha Touchのルールに則ってない書き方をしてるのでソースは参考にしてはいけない

NewImage

iPadアプリでいうUISplitViewController な感じで、それぞれの項目に入力欄や選択肢が出るような感じのモックです。

NewImage

デザイン的なモックでは無いので、フォントサイズを自由に変更できるスライダーを載せて、サイズをいじりながら見られるようにしていました。

NewImage

このSencha Touchの入力部品(テキストエリアやチェックボックス)は、以下のようなJSONオブジェクト的なものを書くだけで作成できるので、かなり手間を省いてできるのでとりあえず並べるものを作るにはとても便利です。入れ子のような構造もちゃんとUIに反映されるのでかなり便利でした。

var data = [{
        title:"年齢",
        instructions:"あなたの年齢を入力してください",
        xtype:'fieldset',
        id:'yearsold',
        items:[
            {
                xtype:"textfield",
                inputType:"number",
                name:'yearsold',
                label:'年齢',
                placeholder:"年齢を入力"
            }
        ]
    },
    {
        title:"一日に吸うタバコの本数",
        instructions:"一日にタバコを吸う本数(禁煙中ならば、以前は何本くらいすっていましたか?)
" + "今まで、合計で何年間くらいタバコを吸っていましたか?", xtype:'fieldset', id:'cigarettes', items:[ { xtype:"textfield", inputType:"number", label:"一日の喫煙本数", name:"cigarettesPerDay" }, { xtype:"textfield", inputType:"number", label:"喫煙年数", name:"yearsOfSmoking" } ] }, { title:"体重・身長", instructions:"あなたの体重と身長を入力してください", xtype:'fieldset', items:[ { xtype:"textfield", inputType:"number", label:"体重", name:"bodyWeight", placeHolder:"体重を入力" }, { xtype:"textfield", inputType:"number", label:"身長", name:"bodyHeight", placeHolder:"身長を入力" } ] }, { title:"チェックシート", instructions:"それぞれの項目に対して該当するものをチェックしてください", xtype:'container', cls:'checksheet', // heightがたりないので無理やり100%に id:'checksheet', scroll:'vertical', items:[ // それぞれの問題 { title:"天候により、せきがひどくなることがありますか?", xtype:'fieldset', defaultType:"radiofield", items:[ { name:'tmp_seki', label:'はい(天候によりひどくなる)', value:'3' }, { name:'tmp_seki', label:'いいえ(天候は関係ない)', value:'0' }, { name:'tmp_seki', label:"せきは出ない", value:'0' } ] }, { title:"風邪をひいていないのにたんが絡むことがありますか?", xtype:'fieldset', defaultType:"radiofield", items:[ { name:'kaze_tan', label:'はい', value:'3' }, { name:'kaze_tan', label:'いいえ', value:'0' } ] }, { title:"朝起きてすぐにたんがカラムことがよくありますか?", xtype:'fieldset', defaultType:"radiofield", items:[ { name:'tan_karamu', label:'はい', value:'0' }, { name:'tan_karamu', label:'いいえ', value:'3' } ] }, { title:"喘鳴(ゼイゼイ、ヒューヒュー)がよくありますか?", xtype:'fieldset', defaultType:"radiofield", items:[ { name:'zenmei', label:'いいえ、ありません', value:'0' }, { name:'zenmei', label:'時々、もしくはよくある', value:'4' } ] }, { title:"今現在(もしくは今まで)アレルギーの症状はありますか?", xtype:'fieldset', defaultType:"radiofield", items:[ { name:'allergy', label:'はい', value:'0' }, { name:'allergy', label:'いいえ', value:'3' } ] }, ] } ];

モックからネイティブアプリに

上記のSencha Touchで作成したモックも参考にしつつ、Objective-CでネイティブのiPadアプリとして作りなおしています。
まだ作成中な感じですが、なんとなく雰囲気だけ残っています…

ネイティブの場合も同じように、JSONで質問項目やメタデータの一覧を書いて、そのJSONを元にUIを作るコード書いて作成しました。
Sencha Touchの場合だと既にJSONからUI(フォーム系)を作るものが用意されているので、この辺がかなり簡略化できていたんだなとObjective-Cで書いてて思いました。

NewImage

NewImage

 

Sencha Touchの感想

Sencha TouchはMVCなど書き方に一見つかみにくい要素があるので少し学習する時間がかかると思いますが、
その辺が上手くつかめるようになれば、jQuery Mobileなどよりもいろいろな事がやりやすかったり、ガッチリしたものも作成できると思います。

Sencha Touch2ではもう少しコードがすっきりしたり、PhoneGapのようなネイティブアプリへのパッケージング機能を持ったり、
よりスマートフォン向けアプリが書きやすくなると思うので、触る機会が増えるかもしれません。

今回のモックを作るにおいて参考したサイトなど

Sencha Touchのインターフェースのレイアウトについてのスクリーンキャスト。
layoutの値による違いを視覚的に把握できるので見ておくべき。
SenchはLearn Sencha Touch | Learn | Senchaなどにスクリーンキャスト等がいろいろあるが、
この中でも特にDrew Neilによるスクリーンキャストはとても解りやすくできているため見ておいて損はない。
スクリーンキャストとしての出来がとても良い感じ Learn Sencha Touch w/ Drew Neil on Vimeo

まだ発売前の書籍ですが、サンプルコードも置かれているのでコンポーネントの表示を確認するのに便利。
ただ、実際のコードはExt.setupなど最低限な構成なため実際に使うときはコピペなどをしないほうがいいと思います。
(後、ライブラリのパスとかがまちまちなので自分で調整しないとまともに見えないサンプルも多い) 
サンプルで公開されているIntroducing Sencha Touch (Green Paper – PDF)には、Sencha Touchにおける構成部品の名前などの図が乗っているので、結構参考になる。

Sencha Touchを使ってノートアプリの作成チュートリアル
モックデザインなど図も多くあり、かなり細かく解説している。(書籍も出してるみたい)

その他の参考になるもの

JavaScript Advent Calendar 2011 (オレ標準コース) : ATND の 5日目の記事です。

最近書いた、スマートフォン向けのJavaScriptライブラリ?(紙芝居のようなものを描画するライブラリ)を書いている時に、テストをどうしようかといろいろ考えていましたが、RequireJS + QUnit + QUnit-TAP + PhantomJS という構成で書くことにしました。
(Jasmineが好きだったのでpavlovMochaも気になっていましたが、上記の構成が安定しそうと思った)
RequireJSが入っているのは、そのライブラリはRequireJSでモジュールを書いていて、r.jsでビルドするような感じにしている為です。

この構成を説明するにあたって、AMDTestingというサンプルプロジェクトを作成したので、それを見ながら書いていきます。

DomModule js   ~ Dropbox workspace toybox AMDTesting  JetBrains WebStorm  WebStorm WS 110 518

シンプルなプロジェクト(AMDTesting)の構成

用意するライブラリとしては以下の2つです。

PhantomJSDownloadするなり$ brew install phantomjsするなりしてインストールしておきます。

QUnit-TAPは以下のようにsubmoduleとしてプロジェクトに追加しています。(アップデート対応とか考えるとコチラのほうがよさそうなので)

➜  AMDTesting ✗ git submodule add git://github.com/twada/qunit-tap.git test/lib/qunit-tap
➜  AMDTesting ✗ cd test/lib/qunit-tap
➜  qunit-tap ✗ git submodule update --init
# QUnit-TapのsubmoduleにQUnitなどが含まれている

qunit-tapのexampleからrunnerとなるphantomjs_test.shとrun_qunit.jsを取り出してtest直下に置いています。
同様にtest直下にQUnitを実際に走らせるページとして以下のような内容のindex.htmlを置きます(ファイル名自体は phantomjs_test.sh に合わせる)

index.htmlのbody以下はQUnitのテンプレート、”requireJSを読み込む”部分より上はQUnit-TAPのテンプレートになっているのであんまり気にしなくても大丈夫です。requireJSで書いたモジュールをテストするのに必要な構成を書いている部分はその間の部分で、
モジュールのパスを楽に書けるように、baseUrl:’spec’でベースとなるパスをspecディレクトリにしています。
また、’src’:’../../src’とすることで、モジュールパスにsrc/hogeと書くと../../src/hogeとなるようにしています。

そして、それぞれのモジュールのテストをモジュールとしてdefineし、index.htmlでまとめて読み込んでいます。
テストする量が多くなったらグループ分けするようなモジュールを作るとイイきがします

AMD形式のモジュールのテストするモジュールについて

例えば、以下のようなモジュールを定義して

// sampleモジュール
define(function () {
    var module = {};
    module.getCharacterAt = function (str, pos) {
        return str.substr(pos - 1, 1);
    }
    return module;
});

次のようにテストするモジュールを書いて、テストコードを追加していきます。

// テスト対象をsrcから読み込んでテストコードを書く
define(["src/sample"], function (module) {
    test("getCharacterAt", function () {
        var str = "STRING";
        equal(module.getCharacterAt(str, 1), "S");
        equal(module.getCharacterAt("STRING", 3), "R");
        equal(module.getCharacterAt(str, str.length), "G");
    });
});

そして、テストを走らせる時はテストモジュールの方を読みこめば、requireJSが依存関係を解決(src/sampleの方をテストモジュール内に読み込んで)してテストを実行できます。

DOMを扱うモジュールのテスト

ここまで触れていませんでしたが、QUnit-TAPのsampleにはPhantomJSに対応したサンプルが含まれいます。
sampleからコピーしてきたphantomjs_test.shというのがまさにそれで、index.htmlを PhantomJS を使って実行します。
PhantomJSはGUIがないWebkitベースのブラウザをコマンドライン動かすようなツールで、DOMを扱ったヘッドレステストも行う事ができます。

今までCUIで完結するテストはEnvjsのようにDOMをエミュレートするか、本物のブラウザを立ち上げて裏で実行させるかのような方法をとっていましたが、PhantomJSはそれがもう少し手軽にできるようになった感じです。(Webkit系の動作だけしかテストできないという欠点もありますが)

次のようなDOMに依存したモジュールのテストも行う事ができます。
まず以下のようなDOMに依存するモジュールがあるとします。


// domModule
define(function () {
    var module = {};
    module.append = function (parentNode, elem) {
        if (elem.nodeType === 1) {
            parentNode.appendChild(elem);
        }
    }
    return module;
});

そして、これをテストするモジュールを作成します。

define(["src/domModule"], function (module) {
    // #qunit-fixture" はreset時に初期化される
    test("append", function () {
        var fixture = document.getElementById("qunit-fixture");
        var addElem = document.createElement("div");
        module.append(fixture, addElem);
        equal(fixture.firstElementChild.nodeName, "DIV");
    });
    test("append-2", function () {
        var fixture = document.getElementById("qunit-fixture");
        var addElem = document.createElement("a");
        module.append(fixture, addElem);
        equal(fixture.firstElementChild.nodeName, "A");
    });
});

QUnitのテンプレートにはid=”qunit-fixture”の空divがありますが、この領域はtest()を実行する度に初期値の内容に書き換えられるようになっているため、htmlの断片などのfixtureを入れる場所として利用できます。
ブラウザでアクセスすれば、上記のテストが実行できるのが通るのがわかると思います。
file:///だと実行できない場合もあるので、

➜  AMDTesting git:(master) ✗ python -m SimpleHTTPServer 8000

という感じで、localhostを立ててブラウザで http://localhost:8000/test/ にアクセスという形をよくとっています。
話を戻して、 PhantomJSを使った場合は、phantomjs_test.shを実行するとブラウザでindex.htmlにアクセスしたのと同じように、コマンドラインからテストが実行できます。

QUnit-TAP

QUnit-TAP : JavaScript のテスティングフレームワークQUnitからTAP出力する – t-wadaの日記

QUnit-TAPはTAP形式のテスト結果を出してくれるため、proveコマンドでテストを実行できます。

➜  test git:(master) ✗ prove -o phantomjs_test.sh

proveでテストを実行できるとCIで回すのが簡単に行えたり、Perlのテスト資産が利用できたり、失敗したテストだけを実行するなどといったことができるようになるので、TAP形式の出力はいろいろと発展性があると思います。

あまり小難しいこと考えなくても、テスト結果がシンプルにわかるから使うとかでもいい気がします。

参考

最初からRequireJSを使うことは決めていて、後からAMDモジュールのテストはどう書くのがいいのかを模索し始めた時に、
以下はAMDモジュールのテスト構成を考える上でとても参考になりました。