latest log

酩酊状態で書いたエンジニアポエムです。酩酊状態で読んでください。

mofmof.js の Unit Test(ver 2) - もっと短く簡単に書けるよ

mofmof.js にはユニットテスト機能があり、同期/非同期テストの混在や遅延評価もシンプルに記述できます。

以下のコードは、http://mofmof-js.googlecode.com/svn/trunk/test/base.js.htm でテストできます。

(ε・◇・)з mofmof.js には Unit Test 機能が最初から付いてるよ
(ε・◇・)з でもね、テスト用のコードは沢山書きたくないよ。リアルに疲れるよ
(ε・◇・)з じゃあ、短く書けるようにしなきゃね!

というわけで、
よりお手軽にするため、先日作成した String#test と Array#test の機能を大幅に強化しました。

おさらい

mofmof.js では、(↓)のように Unit Test を記述できます。

"関数で遅延評価 > 配列の左辺と右辺を評価 > 真偽値で評価".test({
    arg: {
        msg: "hello"
    },
    "関数で遅延評価": function() {
        return this.arg.msg === "hello"; // -> true
    },
    "配列の左辺と右辺を評価": ["aaa", "aaa"], // -> true

    "真偽値で評価": 123 === 123
});

実行結果です。

┌ 関数で遅延評価 > 配列の...()
│ 関数で遅延評価 > 配列の...[ 関数で遅延評価:  true  ]
│ 関数で遅延評価 > 配列の...[ 配列の左辺と右辺を評価:  true = Type_isLike( "aaa", "aaa" ) ]
│ 関数で遅延評価 > 配列の...[ 真偽値で評価:  true  ]
└ 関数で遅延評価 > 配列の...( span: 00.010 )

(ε・◇・)з ロジック量に対してコード量が多いよね。つまりノイズが多いよね
(ε・◇・)з コマンド文字列長いし、テスト項目名を2回も書かなきゃだし…


別名(alias)を指定

テスト項目名に別名(alias)を指定できます。

"長い長い名前:短名" のように

テスト項目名 + : + 別名

でテスト項目名を指定します。

このように指定すると、"長い長い名前" と "短名" の両方がテスト項目名として有効になります。

"長い長い名前:短名 > a-long-long-cat:cat > high-order:hide".test({
    arg: {
        msg: "hello"
    },
    "短名": function() {
        return this.arg.msg === "hello"; // -> true
    },

    cat: ["aaa", "aaa"],        // -> true

    // 二重に指定した場合は "high-order" 側が実行されます
    "high-order": 123 === 123, // -> true
    hide: 123 === 345          // -> この項目は実行されません

}, "別名(alias)を指定可能にしました");


実行結果です。

┌ 別名(alias)を指定可能にしました()      
│ 別名(alias)を指定可能にしました[ 短名:  true  ]
│ 別名(alias)を指定可能にしました[ cat: true = Type_isLike("aaa", "aaa") ]
│ 別名(alias)を指定可能にしました[ high-order:  true  ]
└ 別名(alias)を指定可能にしました( span: 00.004 )


具体的なタイトルを指定

コマンド文字列の代わりに、テストの具体的なタイトルを指定できます。
タイトルを指定しない場合はコマンド文字列がタイトルになります。

"fn1".test({

    fn1: "123" == 123  // -> true

}, "タイトルを指定可能にしました"); // <- タイトル


実行結果です。

┌ タイトルを指定可能にしました()      
│ タイトルを指定可能にしました[ fn1:  true  ]
└ タイトルを指定可能にしました( span: 00.005 )


比較関数を指定

テストを配列で指定した場合に、配列の3番目に比較関数を指定できます。
比較関数を省略すると、Type.isLike で類似検索と深度探索を行います。

mm.has は 集合A に集合B(または要素)が含まれる場合に true になります。

"cat > dog".test({

  cat: ["cat", ["meerkat", "neko", "cat"], mm.has], // -> true
  dog: true,

}, "比較関数を指定可能にしました");

実行結果です。

┌ 比較関数を指定可能にしました()
│ 比較関数を指定可能にしました[ cat:  true = mm_has( "cat", ["meerkat","neko","cat"] ) ]
│ 比較関数を指定可能にしました[ dog:  true  ]
└ 比較関数を指定可能にしました( span: 00.714 )


関数内部から参照する引数を指定

String#test([], title, arg) のように第三引数を指定すると、テスト関数内部から this.arg で参照できます。

var title = "ref arg";
var arg = [0,1,NaN,Infinity,"a","",null,void 0,document,/a/];

"".test([
    mm.has([0], arg),          // -> true
    function() {
        return mm.has(0, this.arg); // -> true
    }
], title, arg); // <- ここです

実行結果です。

┌ ref arg()
│ ref arg[ 0:  true  ]
│ ref arg[ 1:  true  ]
└ ref arg( span: 00.008 )


テストの成功と失敗をハンドリング

String#test.callback を定義しておくと、
テスト成功/失敗の都度 String#test.callback をコールバックします。

callback(result) の形式でコールバックします。result は以下の値をもつ Hash です。

result.ok Boolean 成功でtrue, 失敗でfalse
result.name String テスト項目名
result.msg String 詳細情報
result.pass Number 累積成功数
result.miss Number 累積失敗数
result.items Number テスト項目数
String.prototype.test.callback = function(result) {

    // result = { ok, name, msg, pass, miss, items }

    if (result.ok) {
        mm.log("PASS", mm.dump(result, true));
    } else {
        mm.log.error("MISS", mm.dump(result, true));
    }
};

// 2つ成功し、1つ失敗するテストケース
"".test([
    function(callback) {
        2..lazy(this, function() {
            callback(false);
        });
    },
    "JavaScript".anagram("VBScript"),
    123 === 123
]);

実行結果です。

┌ ()
│ [ 0:  false  ]
MISS {"items":3,"miss":1,"msg":"","name":"0","ok":false,"pass":0}
│ [ 1:  false  ]
MISS {"items":3,"miss":2,"msg":"","name":"1","ok":false,"pass":0}
│ [ 2:  true  ]
PASS {"items":3,"miss":2,"msg":"","name":"2","ok":true,"pass":1}
└ ( span: 02.004 )


コマンド文字列を省略

配列を指定するとコマンド文字列を省略可能です。
順番に実行します。

"".test([
    mm.has(0, [0, 1, 2]),          // -> true
    mm.has([0, 1], [0, 1, 2]),     // -> true
    mm.has([0, 1, "a"], [0, 1, 2]) === false // -> true
]);

実行結果です。

┌ ()
│ [ 0:  true  ]
│ [ 1:  true  ]
└ ( span: 00.007 )

タイトルが無いと、何のテストかわかりませんね。タイトルを指定しましょう。

"".test([
    mm.has(0, [0, 1, 2]),          // -> true
    mm.has([0, 1], [0, 1, 2]),     // -> true
    mm.has([0, 1, "a"], [0, 1, 2]) === false // -> true
], "mm.has のテスト"); // <- title

実行結果です。

┌ mm.has のテスト - mm.has()
│ mm.has のテスト - mm.has[ 0:  true  ]
│ mm.has のテスト - mm.has[ 1:  true  ]
│ mm.has のテスト - mm.has[ 2:  true  ]
└ mm.has のテスト - mm.has( span: 00.09 )


改造前, 改造後

昨日までは(↓)のように沢山タイプしなければいけなかったテストコードが…

"関数で遅延評価 > 配列の左辺と右辺を評価 > 真偽値で評価".test({
    arg: {
        msg: "hello"
    },
    "関数で遅延評価": function() {
        return this.arg.msg === "hello"; // -> true
    },
    "配列の左辺と右辺を評価": ["aaa", "aaa"], // -> true

    "真偽値で評価": 123 === 123
});

String#test のブラッシュアップにより、ここまで短く書けるようになりました。

"".test([
    function() {
        return this.arg.msg === "hello";
    },
    ["aaa", "aaa"],
    123 === 123
], "", { msg: "hello" });

実行結果です。

┌ ()
│ [ 0:  true  ]
│ [ 1:  true = Type_isLike( "aaa", "aaa" ) ]
│ [ 2:  true  ]
└ ( span: 00.09 )

Array#test も上記と同じ結果になります。

[
    function() {
        return this.arg.msg === "hello";
    },
    ["aaa", "aaa"],
    123 === 123
].test("", { msg: "hello" });

(ε・◇・)з 余計なコードが減ってスッキリ ノイズすくない( S/N比高い )
(ε・◇・)з テストマシマシ。コードヘラシヘラシ!



このエントリをより良く理解するには、過去のエントリ

をご覧ください。