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

Table of Contents

しかだよ。

久々に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(<span class="synConstant">&#34;changePxInt function&#34;</span>, <span class="synIdentifier">function</span>()<span class="synIdentifier">{</span>
    <span class="synIdentifier">var</span> test1 = changePxInt(<span class="synConstant">&#34;20px&#34;</span>);
    equal(20, test1, <span class="synConstant">&#34;normal test&#34;</span>);
    <span class="synIdentifier">var</span> test2 = getPxInt(<span class="synConstant">&#34;0px&#34;</span>);
    equal(0, test2, <span class="synConstant">&#34;normal test not number&#34;</span>);
    <span class="synIdentifier">var</span> test3 = getPxInt(<span class="synConstant">&#34;px&#34;</span>);
    equal(0, test3, <span class="synConstant">&#34;abnormal test bad string px&#34;</span>);
    <span class="synIdentifier">var</span> test4 = getPxInt(<span class="synConstant">&#34;20p&#34;</span>);
    equal(0, test4, <span class="synConstant">&#34;abnormal test bad string 20p&#34;</span>);
    <span class="synIdentifier">var</span> test5 = getPxInt(<span class="synConstant">&#34;&#34;</span>);
    equal(0, test5, <span class="synConstant">&#34;abnormal test empty&#34;</span>);
<span class="synIdentifier">}</span>);

});

イベントのテスト

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(){
    <span class="synIdentifier">var</span> $img = $(<span class="synConstant">&#34;&#60;img&#62;&#34;</span>).attr(<span class="synConstant">&#34;src&#34;</span>, <span class="synConstant">&#34;test.jpg&#34;</span>).imgOver(<span class="synConstant">&#34;_o&#34;</span>);

    <span class="synComment">//testを評価するfunction</span>
    <span class="synIdentifier">var</span> over_handler = <span class="synIdentifier">function</span>(<span class="synStatement">event</span>, data)<span class="synIdentifier">{</span>
        equal(<span class="synConstant">&#34;test_o.jpg&#34;</span> , img.attr(<span class="synConstant">&#34;src&#34;</span>), <span class="synConstant">&#34;normal test mouse over&#34;</span>);
    <span class="synIdentifier">}</span>;
    <span class="synIdentifier">var</span> out_handler = <span class="synIdentifier">function</span>(<span class="synStatement">event</span>, data)<span class="synIdentifier">{</span>
        equal(<span class="synConstant">&#34;test.jpg&#34;</span> , img.attr(<span class="synConstant">&#34;src&#34;</span>), <span class="synConstant">&#34;normal test mouse out&#34;</span>);
    <span class="synIdentifier">}</span>;

    <span class="synComment">//bindで評価するfunctionを渡して、</span>
    <span class="synComment">//triggerでイベントを呼ぶ</span>
    $img.bind(<span class="synConstant">&#34;mouseover&#34;</span>, over_handler)
        .trigger(<span class="synConstant">&#34;mouseover&#34;</span>)
        .unbind(<span class="synConstant">&#34;mouseover&#34;</span>, over_handler);
    $img.bind(<span class="synConstant">&#34;mouseout&#34;</span>, out_handler)
        .trigger(<span class="synConstant">&#34;mouseout&#34;</span>)
        .unbind(<span class="synConstant">&#34;mouseout&#34;</span>, out_handler);
<span class="synIdentifier">}</span>);

});

非同期処理のテスト

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

    //testじゃなくてasyncTestを使う。
    asyncTest("find", function(){
    <span class="synComment">//テストしたい非同期な処理</span>
    $.getJSON(<span class="synConstant">&#34;index.<a class="keyword" href="http://d.hatena.ne.jp/keyword/json">json</a>&#34;</span>, <span class="synIdentifier">function</span>(data)<span class="synIdentifier">{</span>
        <span class="synIdentifier">this</span>.model = <span class="synStatement">new</span> modelObj(data);
    <span class="synIdentifier">}</span>);

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

前処理、後処理

テスト毎に前処理後処理は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の仕様をあんまり理解してないので、うまく書けない。(´・ω・`)

とりあえず上記くらいのパターンが書ければだいたいのテストはできると思う。アニメーションのテストは難しそう。今回は書かないことにした。今度本気出す。( ー`дー´)キリッ

まとめサイト

わかりやすい使い方、詳細は以下サイトを見たほうがいいです。

QUnit - jQuery JavaScript Library

jQueryのテスティングフレームワークQUnit (でぃべろっぱーず・さいど)

QUnitの基本的な使い方 - but hopeful