SiestaでMVCアプリのテストを書く

Testing Sencha Apps by Mats Bryntse (Sencha Touch North West Meetup) という動画を見てSiestaに興味を持ったので、さわり始めてみました。 SiestaはJavaScriptのテストフレームワークで、非常にビジュアル系。なんせExt JSで作られていますからね。 Siestaを使って、MVCアプリケーションのテストをしてみたい。 ということで、やり始めているのですが、これはその記録です。 根気よく続いていれば、続きを書けるかもしれません。

この画面は、この記事の最後のテスト、GridPanelのテストを実行したときの画面です。画面上でテストを選択して実行させる。ビジュアルコンポーネントのテストでは、その表示も確認できます。冒頭のビデオを見ていても、とてもワクワクするような作りになっています。

Siestaの配置

  ▾ public_html/
    ▾ ex_siesta/
      ▸ app/
      ▸ data/
      ▸ resources/
      ▾ test/
        ▸ siesta/   <-- SiestaのSDK
        ▸ tests/
          test_harness.html
          test_harness.js
        app.js
        index.html
    ▸ extjs/        <-- Ext JS のSDK

テスト対象のアプリケーションのフォルダにtestフォルダを作って、その下にtestsディレクトリとsiestaのSDKを配置します。 配置の位置関係は自由ですが、僕の場合にはテストを1つのディレクトリにつめこんでしまいたかったので、こうしてみました。こうするとうまくいかないこと(Modelのproxyが参照しているurlの階層の問題)があったので、test下に放り込むのはやめて、Siestaのexampleに従って次のようにしました。

  ▾ public_html/
    ▾ ex_siesta/
      ▸ app/
      ▸ data/
      ▸ resources/
      ▸ siesta/   <-- SiestaのSDK
      ▸ tests/
        test_harness.html
        test_harness.js
        app.js
        index.html
    ▸ extjs/        <-- Ext JS のSDK

テストの起点となるのは、test_harness.html と test_harness.js です。 この場合は、test ディレクトリの下に入れ込んでしまうので、index.js / index.html という名前にしても良いのでしょうね。

test_harness.html

    <!DOCTYPE html>
    <html>
        <head>
            <link rel="stylesheet" type="text/css" href="../../extjs/resources/css/ext-all.css"/>
            <link rel="stylesheet" type="text/css" href="siesta/resources/css/siesta-all.css">
            
            <script type="text/javascript" src="../../extjs/ext-all.js"></script>
            <script type="text/javascript" src="siesta/siesta-all.js"></script>

            <script type="text/javascript" src="test_harness.js"></script>
        </head>
        
        <body>
        </body>
    </html>

CSSとJS、それぞれExt JSとSiestaのものを読み込みます。 最後に、test_harness.jsを読み込みます。

Harness

test_harness.js

    var Harness = Siesta.Harness.Browser.ExtJS;

    Harness.configure({
        title       : 'MVC Test Suite',
        loaderPath  : { 'EX' : 'app' },
        
        preload : [
           "../extjs/resources/css/ext-all.css",
           "../extjs/ext-all-debug.js"
        ]
        
    });

    Harness.start({
        group: 'Sanity',
        items: [
            'tests/010_sanity.t.js'
        ]
    },{
        group: 'Model',
        items: [
            'tests/021_employee.t.js',
            'tests/022_depertment.t.js'
        ]
    },{
        group: 'View',
        items: [
            'tests/031_list.t.js',
            'tests/032_grid.t.js'
        ]
    });

Harnessの設定

  • loaderPath loaderPathには、アプリケーションの名前空間へのパスをセットします。 (Ext.loaderに設定するのと同じですね)
  • preload
    preloadには各テストをロードする前にロードすべきファイルを指定します。

テストコード

実際のテストは、この場合testsディレクトリの下に置きます。 それぞれのテストは、完全に独立して実行されグローバルスコープもクリーンナップされます。

tests/010_sanity.t.js

    StartTest(function(t) {
        t.diag("Sanity test, loading classes on demand and verifying they were indeed loaded.");
        
        t.ok(Ext, 'ExtJS is here');
        t.requireOk('EX.controller.Main');
    });

StartTestという関数の引数に無名関数を渡しています。

テストコードは StartTest(function (t) { .... }) でラップします。 StartTestに渡す関数に第1引数として渡される引数(前記のt)は、Siesta.Testのインスタンスです。 で、Siesta.Test のインスタンスにはいくつかのアサーションメソッドがあります。 それを使ってテストを書いて行きます。ここで使っているのは次のようなものです。

  • okメソッド
    値が真になる場合にテストをパスします
  • requireOkメソッド
    Siesta.Test.ExtJSクラスのメソッド。Ext.require()で指定したクラスをロードして、コールバック関数が指定されていたら、それを実行する。
  • doneメソッド
    ここでは使ってませんが、テストを終了するメソッドです。

Modelのテスト

    StartTest(function(t) {

        t.requireOk('EX.model.Employee', function() {
            var mod = Ext.create('EX.model.Employee', {
                name: '鬼瓦 権三',
                department_id: 1,
                email: 'gonzo@onigawara.com',
                gender: '男',
                age: 33
            });
            
            t.is(mod.genderEn(), 'male', 'genderEn works ok');
            t.is(mod.get('email'), 'gonzo@onigawara.com', 'Could read email');
        });
    });
  • isメソッド
    1つ目と2つ目の引数が等しければテストをパスします

genderEnというメソッドは、Modelで定義しているメソッドで

    genderEn: function(value) {
        var me = this,
            gender = me.get('gender');

        if(gender == '男'){
            return 'male';
        } else if(gender == '女'){
            return 'female';
        } else {
            return gender;
        }
    }

こんな感じのメソッドです。このテストでは、isメソッドでその動作を確認しているのと、 普通にgetメソッドでemailを読み出して、値を比較してみています。

Storeのテスト

    StartTest(function(t) {
        t.requireOk('EX.store.Departments', function() {
            var s = Ext.create('EX.store.Departments'),
                async = t.beginAsync();

            s.load({
                callback: function() {
                    var c, m;

                    t.pass('loaded');
                    c = s.getCount();
                    t.isGreater(c, 0, Ext.String.format('レコードが{0}件', c));
                    m = s.getAt(0);
                    t.ok(m, Ext.String.format('最初のレコードのname:{0}', m.get('name')));
                    t.endAsync(async);
                }
            });
            
        });
    });

ストアのテストでは、ストアをloadしたコールバックでテストを実行しました。

追記) 非同期のコールバックを使ったテストをする場合は、beginAsync/ednAsyncを使って、その非同期呼び出しが完了するまで、 テストが終了しないようにする必要があります。

View のテスト

Gridをテストしてみます。

    StartTest(function(t) {
        t.requireOk(
            [
                'EX.model.Employee', 
                'EX.store.Employees', 
                'EX.model.Department', 
                'EX.store.Departments', 
                'EX.view.Grid'
            ], 
            function() {
                var grid;
                
                Ext.create('EX.store.Employees',{
                    storeId: 'Employees'
                });
                Ext.create('EX.store.Departments', {
                    storeId: 'Departments'
                });

                grid = Ext.create('EX.view.Grid',{
                    renderTo: Ext.getBody()
                });

                t.waitForRowsVisible(grid, function() {
                    t.is(grid.store.getCount(), grid.getView().getNodes().length, 'Rendered all data in store ok');
                    t.matchGridCellContent(grid, 0, 1, grid.store.first().get('name'), 'Found full name in first cell');
                });
            }
        );
    });

テストできるようになるまで、ちょっとハマりました。 上記で、ストアを生成する時に、storeIdを指定しています。テスト対象のストアにはstoreIdの定義はありません。 通常、Gridではstoreコンフィグに文字列でストアを指定しますね。

Ext.define('EX.view.Grid', {
    store: 'Users'
    :
    :
});

こんな感じですよね。ここで指定している ‘Users’ っていうのは、ストアのstoreIdになります。 MVCではstoreIdをつけていなくても、クラス名からstoreIdを生成してストアをStoreManagerに登録してくれますが、 テストの状態ではそんなことはありません。 ですので、ストアをインスタンス化するときにstoreIdを指定してやる必要があります。

で、グリッドを生成してやると、Siestaの画面にちゃんとGridが表示されます。 t.waitForRowsVisible を使ったテストは、Siestaのサンプルの中にあったものです。 こういったExt JSのテストに特化したメソッドが用意されているのはいいですね。

続く(たぶん)

SiestaでViewのテストまで書いてみました。単体でのテストができていますね。 Siestaでは、コントローラーや結合テストもでき、ボタンをクリックさせたりしてテストをすすめることができます。 次回はそういったところを書きたいと思います。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です