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