#freeze [[FrontPage]] #contents 2011/08/02からのアクセス回数 &counter; ** QUnitのインストール [#o56a7494] ひげろぐサイトの[[Titaniumでユニットテスト>http://higelog.brassworks.jp/?p=692]] を参考にデータベース関連の単体テストを試してみます。 [[lukaso/qunit – GitHub>https://github.com/lukaso/qunit]] からダウンロードし、以下のファイルをプロジェクトにコピーします。 - runner.js - qunit以下すべて - test以下すべて app.jsの終わりに以下の行を追加します。 #pre{{ // Qunit Titanium.include('qunit/titanium_adaptor.js'); }} ** データベースの単体テスト [#h3ed15ea] *** record_db.jsへの追加 [#j2735ada] データベースの単体テストを実施するために、record_db.jsに以下の関数を追加しました。 - findById: idで指定されたレコードを取得する - deleteAll: すべてのレコードを削除する - restore: 引数(findAllの戻り値)で与えられたレコードにテーブルを戻す #pre{{ this.findById = function(id) { this.open(); var rows = this.db.execute( 'SELECT * FROM records WHERE id=?', id ); var res = this.setRows(rows); rows.close(); this.close(); if (res.length > 0) return res[0]; else return null; }; this.deleteAll = function() { Ti.API.debug("deleteAll"); this.open(); var rows = this.db.execute( 'DELETE FROM records' ); this.close(); return true; }; this.restore = function(records) { Ti.API.debug("restore"); this.deleteAll(); var last = records.length-1; for (i = last; i >= 0; i--) this.insert(records[i]); } }} *** データベース単体テストの追加 [#g394fdc4] 準備ができたので、データベース単体テスト(test/record_db_test.js)を追加します。 データを1個挿入するテストをします。 - 最初にsavedにデータベースを保存し、最後にリストア-処理を書きます。 - test関数で、単体テストを記述します。QUnit.equivを使うと複雑なデータ構造の比較が簡単に行えます。 record_db_test.js #pre{{ Ti.include('../record_db.js'); var db = new RecordDB(); var saved = db.findAll(); test("insert one", function() { expect(2); var expected = { "id": 1, "weight": "60", "at": "Sun Jul 31 2011" }; db.deleteAll(); db.insert(expected); var one = db.findById(1); notEqual(null, one, "not null"); equal(QUnit.equiv(one, expected), true, "check one"); }); db.restore(saved); Ti.API.debug(QUnit.jsDump.parse(saved)); db = null; }} 次に、test/tests_to_run.jsでrecord_db_test.jsをインクルードします。 #pre{{ // Tests to run //Titanium.include('same.js'); //Titanium.include('test.js'); Titanium.include('record_db_test.js'); }} *** 単体テストの実行 [#f18a4417] アプリケーションを動かして実際に単体テストを実行します。 ソースの修正箇所は、単体テストの結果は、logに出力されます。Debug用のログを除くと、 次のようになります。 #pre{{ [INFO] >> >> >>TEST START: insert one [INFO] <span class="test-message">not null</span>, expected: <span class="test-expected"> { "id": 1, "weight": "60", "at": "Sun Jul 31 2011" }</span> result: <span class="test-actual">null</span>, diff: <del>{ </del><del>"id": </del> <del>1, </del><del>"weight": </del><del>"60", </del><del>"at": </del> <del>"Sun </del><del>Jul </del><del>31 </del><del>2011" </del><del>} </del> <ins>null </ins> [INFO] <span class="test-message">check one</span>, expected: <span class="test-expected">true</span> [INFO] << << <<TEST DONE : insert one FAILURES: 0 out of TOTAL: 2 [INFO] DONE : FAILURES: 0 out of TOTAL: 2 }} ちょっと読みづらいですが、DONE : FAILURES: 0 out of TOTAL: 2でエラー無く終了したことが確認できます。 *** 他のテストも追加 [#k857c2e5] 上手くQUnitでテストできることを確認したので、insert two, update two, delete twoのテストを追加います。 JSON形式でテストデータや予想データを記述できるので、データベースのような複雑なテストも容易に記述することができます。 #pre{{ test("insert two", function() { expect(1); var expectedAll = [{ "id": 2, "weight": "62", "at": "Tue Aug 02 2011" }, { "id": 1, "weight": "60", "at": "Sun Jul 31 2011" }]; var expectedTwo = { "id": 2, "weight": "62", "at": "Tue Aug 02 2011" }; db.insert(expectedTwo); var all = db.findAll(); equal(QUnit.equiv(all, expectedAll), true, "check using equal") }); test("update two", function() { expect(1); var updatedTwo = { "id": 2, "weight": "65", "at": "Tue Aug 02 2011" }; db.update(updatedTwo); var two = db.findById(2); equal(QUnit.equiv(two, updatedTwo), true, "check two"); }); test("delete two", function() { expect(1); var expectedAll = [{ "id": 1, "weight": "60", "at": "Sun Jul 31 2011" }]; var two = { "id": 2, "weight": "65", "at": "Tue Aug 02 2011" }; db.deleteOne(two); var all = db.findAll(); equal(QUnit.equiv(all, expectedAll), true, "check all"); }); }} *** エラーの場合 [#qb6fda1b] 単体テストでエラーが発生した場合には、ERRORログに出力されるため、コンソースに赤く表示されます。 &ref(error.png); ** お詫び [#ic9a2788] QUnitの調査を進めた結果、データベースの時刻が20秒ほど前後する障害の原因が、 Ti.App.fireEventで送った時にDateの時刻がずれることが分かりました。 また、QUnit.equalsでDate型が正しく処理できないこともあり、recordのatの型 Date型からString型に変更することにしました。 record_db_test.js #pre{{ this.setRows = function (rows) { var res = []; if ( rows.getRowCount() > 0 ) { Ti.API.debug('Found: ' + rows.getRowCount() ); for (i =0; rows.isValidRow(); i++) { var record = {}; record.id = rows.fieldByName('id'); record.weight = rows.fieldByName('weight'); var time = rows.fieldByName('at', Titanium.Database.FIELD_TYPE_DOUBLE) var at = new Date(); at.setTime(time); record.at = at.toDateString(); res.push(record); rows.next(); } } return res; }; this.update = function(record) { this.open(); var at = new Date(record.at); var res = this.db.execute( 'UPDATE records SET weight=?, at=? WHERE id=?', record.weight, at.getTime(), record.id ); Ti.API.debug('Update DB'); this.close(); return true; }; this.insert = function(record) { this.open(); var at = new Date(record.at); var res = this.db.execute( 'INSERT INTO records (weight, at) VALUES(?,?)', record.weight, at.getTime() ); Ti.API.debug('Insert into DB'); this.close(); }; }} table_view.js #pre{{ function updateRecord (records) { のdateLabelの設定部分 var dateLabel = Ti.UI.createLabel({ width: 290, height: 'auto', left: 5, top: 5, fontSize: 6, textAlign: 'right' } ); dateLabel.text = record.at; row.add(dateLabel); 追加ボタンのコールバック addButton.addEventListener( 'click', function () { var at = new Date(); var recordWindow = Ti.UI.createWindow({ url: 'record_window.js', record: {weight:'', at: at.toDateString()}, func: 'insert_row', backgroundColor:'#fff' }); Ti.UI.currentTab.open(recordWindow); }); }} record_window.js #pre{{ var dateField = Ti.UI.createTextField({ value: win.record.at, hintText: '日付を入力してください', top:20, left:50, right:50, height:40, borderStyle: Ti.UI.INPUT_BORDERSTYLE_ROUNDED }); win.add(dateField); saveButton.addEventListener( 'click', function () { var record = {}; record.id = win.record.id; record.weight = weightField.value; var at = new Date(dateField.value) record.at = at.toDateString(); Ti.App.fireEvent(win.func, record); win.close(); }); }} ** プログラムソース [#n5955ebc] ここまでのプログラムソースは、 - &ref(Demo.zip); にまとめてあります。 ** コメント [#uda5179f] #vote(おもしろかった[1],そうでもない[0],わかりずらい[0]) #vote(おもしろかった[1],そうでもない[0],わかりずらい[1]) 皆様のご意見、ご希望をお待ちしております。 #comment_kcaptcha