javascript、いや、jQueryのテストツールQUnit使ってみた

しかだよ。
久々にajaxなお仕事に携わってjavascript(むしろjQuery)書きました。んでテストコード書きたいので調べたらJSUnitとQUnitを見つけました。QUnitjQueryから派生したツールらしいので迷わずQUnitにしました。jQueryのテストが簡単に書けるのでいいですね。

functionのテスト

テストはこんな感じ。たぶん動くよ。

//テスト対象のサンプルコード
/*
 * px文字列を数値にする。
 * sample "100px" -> 100
 */
var changePxInt = function(px_str){
    if('number' == typeof(px_str)){
        return px_str;
    }
    var index = px_str.search("px");
    if(index < 0){
        return 0;
    }
    return Number(px_str.slice(0,index));
};
//テストコード
$(function(){
    module("function test");

    test("changePxInt function", function(){
        var test1 = changePxInt("20px");
        equal(20, test1, "normal test");
        var test2 = getPxInt("0px");
        equal(0, test2, "normal test not number");
        var test3 = getPxInt("px");
        equal(0, test3, "abnormal test bad string px");
        var test4 = getPxInt("20p");
        equal(0, test4, "abnormal test bad string 20p");
        var test5 = getPxInt("");
        equal(0, test5, "abnormal test empty");
    });
});

イベントのテスト

clickとかhoverとかのイベントのテストはこんな感じ。bindとtriggerでイベントを意図的に呼んでテストする感じ。

//テスト対象のサンプルコード
$(function(){
    /*
     * 画像へのマウスオーバーで画像を変換する。
     */
    $.fn.imgOver = function(param){
        $(this).hover(function(){
            var src = $(this).attr("src");
            var prefix  = src.slice(src.lastIndexOf('.'));
            var name = src.slice(0,src.lastIndexOf('.'));
            var onname = name + param + prefix;
            $(this).attr("src",onname);
        },function(){
            var src = $(this).attr("src");
            var outname = src.replace(param,"");
            $(this).attr("src",outname);
        });
        return this;
    };
});
//テストコード
$(function(){
    test("hover", function(){

        var $img = $("<img>").attr("src", "test.jpg").imgOver("_o");

        //testを評価するfunction
        var over_handler = function(event, data){
            equal("test_o.jpg" , img.attr("src"), "normal test mouse over");
        };
        var out_handler = function(event, data){
            equal("test.jpg" , img.attr("src"), "normal test mouse out");
        };

        //bindで評価するfunctionを渡して、
        //triggerでイベントを呼ぶ
        $img.bind("mouseover", over_handler)
            .trigger("mouseover")
            .unbind("mouseover", over_handler);
        $img.bind("mouseout", out_handler)
            .trigger("mouseout")
            .unbind("mouseout", out_handler);
    });
});

非同期処理のテスト

例えばjsonを取得してそのデータを元にごにょごにょする場合は、テストの実行と、データを取得するまでのズレが発生するのでsetTimeoutでいい感じに調整する。

    //testじゃなくてasyncTestを使う。
    asyncTest("find", function(){

        //テストしたい非同期な処理
        $.getJSON("index.json", function(data){
            this.model = new modelObj(data);
        });

        //第3引数に数値を渡し、実行するタイミングを遅らせる。とりあえず100mにしてみた。
        setTimeout(function(){
            //100ms経ったら動き出す!
            start();
            var test1 = this.model.find("test");
            equal("test", test1.name, "normal test");
            var test2 = this.model.find("shikajiro");
            equal(undefined, test2, "abnormal test");
        },100);
    });

前処理、後処理

テスト毎に前処理後処理はmoduleの第2引数にfunctionを定義できる。setupに前準備の処理、teardownにテスト後の処理(オブジェクトの後始末とか)を書く。

    module("async test",{
        setup: function() {
            $.getJSON("index.json", function(data){
                this.model = new modelObj(data);
            });
        },
        teardown: function(){
            this.model = undefined;
        }
    });

感想

今回はテストファーストじゃなくて実装した後にテストコード書いたんだけど、テストコードを書けば書くほど設計の悪さが浮き彫りになりました。やっぱテストは大事ですね。リファクタリングも出来るようにもなったし大満足。javascriptの仕様をあんまり理解してないので、うまく書けない。(´・ω・`)
とりあえず上記くらいのパターンが書ければだいたいのテストはできると思う。アニメーションのテストは難しそう。今回は書かないことにした。今度本気出す。( ー`дー´)キリッ