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

[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> [/html]

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

Harness

test_harness.js

[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’ ] }); [/js]

Harnessの設定

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

テストコード

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

tests/010_sanity.t.js

[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’); }); [/js]

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

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

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

Modelのテスト

[js] 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’); }); }); [/js]
  • isメソッド
    1つ目と2つ目の引数が等しければテストをパスします

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

[js] genderEn: function(value) { var me = this, gender = me.get(‘gender’); if(gender == ‘男’){ return ‘male’; } else if(gender == ‘女’){ return ‘female’; } else { return gender; } } [/js]

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

Storeのテスト

[js] 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); } }); }); }); [/js]

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

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

View のテスト

Gridをテストしてみます。

[js] 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’); }); } ); }); [/js]

テストできるようになるまで、ちょっとハマりました。 上記で、ストアを生成する時に、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では、コントローラーや結合テストもでき、ボタンをクリックさせたりしてテストをすすめることができます。 次回はそういったところを書きたいと思います。