テスティングフレームワーク
JavaScriptのテスティングフレームワークを調べたのですがあまりいいのがなさげです。調査したのは以下の2つ。
- JsUnit
- テストケースの実行が面倒。(実行ページを開く→テストケース選択→実行という手順が必要。)
- テストケースをHTMLにするのが面倒。
- テストケースごとに依存モジュールをインポートするのが面倒。
- MochiKitのテストフレームワーク
というわけで、テストを実行するTestRunnerだけ自作してみました。
要件と戦略
サンプル
テストケースは以下のように書きます。
// テストケース HogeTest = function() { // テスト名 / 実行結果を識別するキーとして使用される。 this.name = "HogeTest"; } HogeTest.prototype = { // 前準備。テストメソッドの実行前に実行される。 setUp: function() { }, // 後始末。テストメソッドの実行後に実行される。 tearDown: function() { }, // 成功するテストメソッド testSuccess: function() { assertEquals( 1, 1 ); }, // 失敗するテストメソッド testFail: function() { assertEquals( 1, 0 ); } }
テストを実行するメインのHTMLは次のような感じ。これをブラウザで開くとテストの実行結果が表示されます。
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Tests Results</title> <!-- 依存ファイルの取込 --> <link rel="stylesheet" type="text/css" href="./runner/test.css"> <script type="text/javascript" src="./runner/jsUnitCore.js"></script> <script type="text/javascript" src="./runner/TestRunner.js"></script> <script type="text/javascript" src="./runner/prototype.js"></script> <!-- テストケース --> <script type="text/javascript" src="./HogeTest.js"></script> <script type="text/javascript" src="./FooTest.js"></script> </head> <body> <div id="result"></div> <script type="text/javascript"> var runner = new TestRunner( [ // ここにテストケースを追加する。 new HogeTest(), new FooTest() ]); var result = runner.run(); $("result").innerHTML += TestRunner.toHtmlTable(result); </script> </body> </html>
実装
適当すぎてゴメンナサイ。
/** * テストランナー * @param {Array} tests 実行するテストの配列 */ TestRunner = function ( tests ) { this.tests = tests; this.result = {}; } TestRunner.prototype = { /** * テストを実行して結果を返す。 */ run: function () { var thiz = this; this.tests.each ( function(test) { var res = {}; // 実行結果。 for ( func in test ) { // 関数でないものは実行しない if ( (typeof test[func] ) != "function" ) { continue; } // 名前が「test*」でない関数は実行しない。 if ( !func.match(/test(.+)/) ) { continue; } try { // 実行準備 if (test.setUp) { test.setUp(); } // 実行 var tmp = thiz.runInner( test, func ); if ( tmp ) { res[func] = tmp; } } finally { // 後始末 if (test.tearDown) { test.tearDown(); } } } // テスト名をキーにして結果を追加。 thiz.result[test.name] = res; }); return this.result; }, /** * テストの内部処理 * @param {Object} test テスト * @param {Object} func テストのメソッド */ runInner: function ( test, func ) { try { test[func](); return { success: true }; } catch ( err ) { return { success: false, error: err }; } } } /** * テストの実行結果をHTMLのテーブルにする。 * @param {Object} result 実行結果 */ TestRunner.toHtmlTable = function ( result ) { var str = "<table bolder='1' class='result'>"; $H(result).each ( function( item ) { str = str + "<tr>"; str = str + "<td colspan='2'><span class='testname'>" + item[0] + "</span></td>"; str = str + "</tr>"; $H( item[1] ).each ( function( func ) { if ( func[1]["success"] ) { str = str + "<tr class='success'>"; } else { str = str + "<tr class='fail'>"; } str = str + "<td>" + func[0] + "</td>"; if ( func[1]["success"] ) { str = str + "<td>success.</td>"; } else { str = str + "<td>fail.<br/>"; if ( func[1]["error"].jsUnitMessage ) { str = str + func[1]["error"].jsUnitMessage; str = str + "<pre>" + func[1]["error"].stackTrace + "</pre>"; } else { str = str + func[1]["error"].message + "<br/>"; str = str + func[1]["error"].fileName + ":" + func[1]["error"].lineNumber ; str = str + "<pre>" + func[1]["error"].stack + "</pre>"; } str = str + "</td>"; } str = str + "</tr>"; } ); }); str = str + "</table>"; return str; }