無料で使えるシステムトレードフレームワーク「Jiji」 をリリースしました!

・OANDA Trade APIを利用した、オープンソースのシステムトレードフレームワークです。
・自分だけの取引アルゴリズムで、誰でも、いますぐ、かんたんに、自動取引を開始できます。

テスティングフレームワーク

JavaScriptのテスティングフレームワークを調べたのですがあまりいいのがなさげです。調査したのは以下の2つ。

  1. JsUnit
    • テストケースの実行が面倒。(実行ページを開く→テストケース選択→実行という手順が必要。)
    • テストケースをHTMLにするのが面倒。
    • テストケースごとに依存モジュールをインポートするのが面倒。
  2. MochiKitのテストフレームワーク
    • assertメソッドがxUnitシリーズと違っていて使いにくい。(JUnitになれすぎているので)
    • グローバルな名前空間を汚されるのが我慢ならん。

というわけで、テストを実行するTestRunnerだけ自作してみました。

要件と戦略

  • assertメソッドはJsUnitの関数をありがたく使わせていただく。TestRunnerだけ自作。
  • テストケースはjsファイルに書く。
  • テスト実行はメインのHTMLを開くだけ。「テスト実行用HTMLをブラウザで開く→即テストケース実行→結果を表示」としたい。
  • 複数のテストをまとめて実行できるようにする。
  • テストの成否とエラーの詳細は最低限見えるように。

サンプル

テストケースは以下のように書きます。

// テストケース
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;
}