0-9
JavaScript UnitTest Patterns

JavaScript UnitTest Patterns

ここでは以下の順番でSinonJSとJsTestDriverを使用したJavaScriptのUnitTest Patternsを紹介します。

初期化の遅延

UnitTestを行う場合、まずは初期化functionが自動的に実行されないようにしましょう。

初期化functionをこちらの任意のタイミングで実行できるようにすることにより事前に対象外のコードをstub化したり、必要なfunctionへspyを仕込んだ状態でfunctionを実行できるようになります。

UnitTestの場合のみ初期化を遅延する

一番簡単な方法はUnitTest実行時のみ条件分岐で初期化を止める方法です。

    $(function () {
        if (window.sinon) {
            init();
        }
    });

この方法は簡単かつ確実にタイミングを制御できますが、元コードを変更する必要があるため後からテストを追加する場合に問題になる可能性があります。

addEventListener経由での設定

window.onloadやDOMContentLoaded経由で初期化を行なっている場合、window.onloadやdocument.addEventListenerをstub化します。

    //元functionを保持
    var _addEventListener = document.addEventListener;

    //stub化
    sinon.stub(document, 'addEventListener');

    //stubが呼び出されるので実行されない
    document.addEventListener('DOMContentLoaded', function () {
    }, false);

window.onloadやdocument.addEventListener自体のテストが行いたい場合、clickイベントを強制的に発生させたい (fireEvent/createEventの使い方) - 主に言語とシステム開発に関して等を参考にして直接ブラウザのイベントを呼び出してください。

jQuery経由での設定

初期化にjQueryを使用する場合先にjQuery functionをstub化し、functionが引数の場合に呼び出しを行わないようにすることができます。

この方法は「sinon.js -> jQuery -> 以下の初期化コード -> テスト対象コード」の順番でコードを読み込むことで実現できます。

    //元jQuery objectを保管
    var _jQuery = jQuery;

    //jQuery, $の両方stub化する必要があるので注意
    sinon.stub(window, 'jQuery', init_stub);
    sinon.stub(window, '$', init_stub);

    //jQueryのfunction propertyは自動で継承されるので、このfunctionは呼び出し時の動作のみ想定すればOK
    function init_stub (arg) {
        // functionの場合何もしない
        if (_jQuery.isFunction(arg)) {
            return;
        }
        return _jQuery.apply(this, arguments);
    }

こうすることで$(function () {})経由で設定されたfunctionを自分の好きな段階で$.args[n][0]()から呼び出すことができます。

script要素内に書かれたコードのテスト

htmlに直接書かれたコードもhtmlを$.ajax等で文字列として取得後、正規表現で切り出してevalすることでテストできますが、この方法はあまり安全ではないためhtmlとJSを分離することを推奨します。

非同期実行の同期化

主なUnitTest Frameworkは非同期実行のテストをサポートしていますが、UnitTestを行う場合できる限り非同期コードを同期的に実行することを推奨します。

非同期実行コードがテストに含まれる場合テストが複雑になるだけでなく、テストが増えてきた際に非同期部分の実行時間がテストの実行時間を伸ばすことになります。
(同期テストのみであればテスト実行環境を高速化することでテスト時間を短縮できますが、必ず固定秒数かかるテストが複数あった場合、それが積み重なるとテスト時間が非常に長くなる上に短縮できなくなります)

setTimeout、setIntervalの同期化

ShinonJSのuseFakeTimersを使うことではsetTimeout、setIntervalを同期化することができます。

    //setTimeout、setIntervalのmock化
    this.clock = sinon.useFakeTimers();

    //mock化されたsetTimeoutの呼び出し(この段階では呼び出されない)
    setTimeout(function () {
        window.alert('ok');
    }, 0);

    //100ms経過したものとする(100ms以内に実行されるfunctionのみ実行される)
    this.clock.tick(100);

useFakeTimersはjsunitのjsUnitMockTimeout.jsを元にしているので興味ある方はソースを読むことをおすすめします。
(コメント込みで150行程度なので簡単に読めると思います)

ただ、sinonのuseFakeTimersを使う場合、Date等も上書きされてしまうためUnitTest Frameworkが対応していない場合は注意してください。
Mochaを使用している場合不具合が出ることがあるそうです)

また、useFakeTimersを使用するとDateは常に0(1970-1-1 00:00:00)を返すようになります。

XHR

ShinonJSのuseFakeXMLHttpRequest、fakeServerを使うことでサーバアクセスをstub化することができます。
(ただ、jQuery等を使用している場合は$.ajaxをstub化してしまう方が簡単かもしれません)

ちなみに、同期実行には出来ませんが、JsTestDriver等には擬似的にサーバの代わりをする機能もあるためそちらを使ってテストを行うことも可能です。

jsDeferred

もし内部でjsDeferredを使用している場合、初期化の段階で以下のコードを実行することでjsDeferredがsetTimeoutを使うようになります。

    Deferred.next = Deferred.next_default;

jsDeferredはブラウザによってより高速な非同期化のコードを使用しますが、上記コードを使うことによりuseFakeTimers経由で実行のタイミングを制御することができます。

html, cssのテスト

htmlの記述

テスト対象のコードがDOM上の要素を参照する場合、テスト環境でも同じようにDOM上に要素が必要な場合があります。

これに関してJsTestDriverではコード内に以下の様なコメントでhtml要素を記述することができます。

    /*:DOC += <div>この要素はdocument.body直下に展開される</div> */
    /*:DOC += <div>
        改行も書けます
    </div> */
    /*:DOC hoge = <div>この要素はthis.hogeで参照できる</div> */
    /*:DOC fragment = <div>要素を並列に記述した場合</div><div>documentFragmentになるので注意</div> */

この内容はコード上の任意の場所に記述でき、ブラウザに配信される前に記述された部分が該当要素を生成するJSに置き換えられます。

注意点として、+=で要素をdocument.body以下に展開する場合、「hoge = 」で変数に生成場合に比べてテストの実行時間が長くなる傾向にあります。
(ブラウザの処理コストがかかる)

これに関して、jQuery等のライブラリを使用している場合、以下のようにセレクタを外部から書き換えられるようにすることで、テスト時には変数に生成された要素を使用することができます。

    var selectors = {
        'link' : 'a'
    };
    $.fn.setLinkText = function () {
        $(selectors.link).html('hoge');
    };

    TestCase('test case', {
        'test_link' : function () {
            /*:DOC link = <a></a>*/
            selectors.link = this.link;
            $.fn.setLinkText();
            assertEquals($(this.link).html(), 'hoge');
        }
    });

また、BusterJSにはtestbedという機能があり、テストに対してそれが実行されるhtmlを指定する機能があります。

htmlの参照

htmlのテストとは「要素が正しく生成されるか」というテストになりますが、htmlのテストはクロスブラウザでの安定した要素のシリアライズが難しいため、テスト結果の比較が難しいという問題があります。

これには以下の様な方法でテストを行うことで安定したテストを行うことができます。

  1. htmlを展開するmethodをstub化する
    jQueryを使用している場合、jQuery.fn.htmlをstub化することでDOMに展開される前のhtmlを受け取る事ができます。
    この方法により、htmlをDOMではなく文字列として扱えるため、クロスブラウザで安全にテストを行うことができます。
  2. HTMLElementのmockを使用する
    もしテスト対象のコードがstyle等の属性を設定している場合、以下のようなmockを使用することでテストを行うことができます。
       var elem = { 'style' : {} };
       targetFunction(elem);
       assertEquals(elem.style.display, 'block');
    
  3. selectorで数える
    上記のような方法が取れない場合、最後は普通にDOM経由で参照して要素数を数えたり、属性値を比較することになります。

css

cssのテストも上記「htmlの参照」と同じように「ライブラリをstub化する」、「mockを使用する」、「直接比較する」のいずれかを行うことで安定したテストを行うことができます。

また、jQuery等のライブラリはブラウザ毎に発生するCSS結果の誤差をまとめる機能もあるため、たとえテスト対象のコードでライブラリを使用していなくてもテスト環境のみクロスブラウザ用ライブラリを使用する方法もあります。

イベントのテスト

イベントのテストは大きく「ブラウザ環境のテスト(正しくイベントが呼ばれるか)」と「イベントコードのテスト(イベント内のコードが意図した動作を行うか)」に分けられます。

ブラウザ環境のテスト

ブラウザ環境のテストは「あるイベントを呼び出した時に正しくイベントが呼ばれるか」をテストするものです。

今回はアプリケーションのテストをメインに考えているので、ブラウザ環境自体のテストに関しては省略します。

イベントコードのテスト

HTMLElement等の要素にbindされたコードをテストする場合、bindを行うコードを呼び出す前にテスト用の要素を作成し、コードを呼び出した後にイベントを呼び出します。

通常のイベントに関しては呼び出しが同期でおこるので、テストの際に呼び出しのタイミングを考慮する必要はありません。

jQuery等のライブラリを使用しているのであれば要素を指定してイベントを発火する仕組みを使用するのが簡単です。
(ライブラリを使用していない場合はclickイベントを強制的に発生させたい (fireEvent/createEventの使い方) - 主に言語とシステム開発に関してを参照してください)

    function bindClick () {
        $('a').click(function () {
            console.log('ok');
        });
    }
    /*:DOC += <a></a>*/

    sinon.stub(console, 'log');

    bindClick();
    $('a').click();

    assertCalledOnce(console.log);

もしjQuery等のライブラリを使用していない場合や、mouseover等で座標を取得している場合などは、イベントをエミュレートするよりイベントと処理を切り離してそれぞれテストするのがお勧めです。

    function bindEvent () {
        $('a').mouseover(function (e) {
            setElement(e.pageX, e.pageY);
        });
    }
    function setElement (X, Y) {
        console.log(X, Y);
    }
    /*:DOC += <a></a>*/

    sinon.stub(window, 'setElement');

    bindEvent();
    $('a').mouseover();

    assertCalledOnce(setElement);

    // .restoreでstubを元functionに戻すことができます
    setElement.restore();
    sinon.stub(console, 'log');

    setElement(1, 2);

    assertCalledOnce(console.log);
    //最初に呼ばれた(args[0])時のargumentsの比較
    assertEquals(console.log.args[0], [1, 2]);

その他問題になりうるコード

document.write

JsTestDriverを使う場合、テストはページリロード無しに実行されるためテスト対象のコードは基本的にDOMContentLoaded後に実行されます。

そのためdocument.writeを使うコードをテストする際は、事前にdocument.writeをstub化してテストを実行しましょう。

alert, confirm

コードの実行が止まってしまうためstub化しましょう。

location

location objectは上書き(stub化)できないため、これに直接依存するコードをテストする場合には問題になる可能性があります。

これに関しては安定した回避方法がないため、テスト対象のコードを書き換えることを推奨します。
(別のobject経由で操作するようにし、テスト実行時はそのobjectをstub化しましょう)

SinonJSとJsTestDriverを使ったJSテスト手法に関して

最近SinonJSとJsTestDriverを組み合わせてこんな感じのコードをベースにテストを書いているので紹介したいと思います。

    sinon.log = function (message) {
        jstestdriver.console.log(message);
    };
    sinon.assert.expose(this, {
        'includeFail' : false
    });
    var oldTestCase = TestCase;
    TestCase = function (name, condition, opt_proto) {
        if ('function' !== typeof condition) {
            opt_proto = condition;
            condition = undefined;
        }
        Object.keys(opt_proto).forEach(function (key) {
            if (!key.match(/^test_/)) {
                return;
            }
            var func = opt_proto[key];
            if (!func.length) {
                return;
            }
            opt_proto[key] = function (queue) {
                queue.call(function (callbacks) {
                    var arg = [];
                    for (var i = 0, l = func.length; i < l; i++) {
                        arg.push(callbacks.add(function () {}));
                    }
                    func.apply(this, arg);
                });
            };
        });
        opt_proto = sinon.testCase(opt_proto);
        if (condition) {
            ConditionalAsyncTestCase(name, condition, opt_proto);
            return;
        }
        AsyncTestCase(name, opt_proto);
    };

    function assertNotClassName(msg, className, element) {
        var args = argsWithOptionalMsg_(arguments, 3);
        var actual = args[2] && args[2].className;
        var regexp = new RegExp('(^|\\s)' + args[1] + '(\\s|$)');

        var flag;
        try {
            assertMatch(args[0], regexp, actual);
            flag = true;
        } catch (e) {
        }
        if (!flag) {
            return true;
        }
        actual = prettyPrintEntity_(actual);
        fail(args[0] + 'expected class name not to be included ' + prettyPrintEntity_(args[1]));
    }

    function assertDeferred(msg, actual) {
        var is_defer = Deferred.isDeferred(actual);

        var args = argsWithOptionalMsg_(arguments, 2);
        jstestdriver.assertCount++;

        if (!Deferred.isDeferred(args[1])) {
            fail(args[0] + 'expected Deferred but was ' + typeof args[1]);
        }
        return true;
    }

    function assertNotDeferred(msg, actual) {
        var is_defer = Deferred.isDeferred(actual);

        var args = argsWithOptionalMsg_(arguments, 2);
        jstestdriver.assertCount++;

        if (Deferred.isDeferred(args[1])) {
            fail(args[0] + 'expected not Deferred but was ' + typeof args[1]);
        }
        return true;
    }

sinon.log

SinonJS内部でエラーがあった場合、エラーメッセージを引数に呼び出されます。

JsTestDriverを使用している場合はjstestdriver.console.logへ出力すると、ブラウザのコンソールへは出力されずにJsTestDriver clientを呼び出したコンソールへエラーメッセージが出力されます。

sinon.assert.expose

SinonJSのAssert methodをsinon.assert.calledではなくassertCalledのように呼び出すことができます。
(第一引数に対してassertCalled等のmethodを追加する)

第二引数の「’includeFail’ : false」は「thisに対してfail methodを追加しない」という指定で、この指定がない場合JsTestDriverのfail methodを上書き指定しまいJsTestDriverが正常に動作しなくなります。
(初期値true)

第二引数は他に「’prefix’ : ‘assert’」も指定でき、第一引数に対してmethodを追加する際のprefixになります。これを「’prefix’ : ‘sinon’」と指定すると「sinon.assert.called」を「sinonCalled」と呼び出すことができます。
(初期値assert)

TestCase = function (name, condition, opt_proto) {}

標準のTestCase methodを乗っ取ります。

もともと別のtest case methodを定義していましたが、名前を変えるとIDEのサポートがなくなるので上書きしました。
IDEのサポートを気にしない場合別の名前を定義するほうがいいと思います。

        if ('function' !== typeof condition) {
            opt_proto = condition;
            condition = undefined;
        }

condition optionが渡されているかどうかを判断します。
condition optionが指定されている場合、後でConditionalAsyncTestCaseを呼び出します。

        Object.keys(opt_proto).forEach(function (key) {
            if (!key.match(/^test_/)) {
                return;
            }
            var func = opt_proto[key];
            if (!func.length) {
                return;
            }
            opt_proto[key] = function (queue) {
                queue.call(function (callbacks) {
                    var arg = [];
                    for (var i = 0, l = func.length; i < l; i++) {
                        arg.push(callbacks.add(function () {}));
                    }
                    func.apply(this, arg);
                });
            };
        });

test methodに引数が指定されている場合、非同期実行用のcallbacksを生成して渡します。

具体的には非同期テストを以下のように記述することができます。

    TestCase('asynch', {
        'test_asynch' : function (callbacks) {
            $.get('/hoge/', function (data) {
                assertEquals(data, 'hoge');
                callbacks();
            });
        }
    });

optproto = sinon.testCase(optproto);

test method objectにsinon.testCaseを実行します。

sinon.testCaseは引数にobjectを渡すことでobject内の各function propertyにsinon.test(function)を実行します。

sinon.testとは引数のfunctionにsinon.sandboxをbindして返します。

これを使うことにより以下のような利点があります。

  1. useFakeTimers, useFakeServerが自動で実行される

    setUp内でthis.clock, this.serverが自動的に定義され、tearDown内で自動的にrestoreされます。

  2. this.spy, this.stub, this.mockが定義される

    それぞれ呼び出されたtest methodの実行終了後に自動でrestoreされるfunctionを返します。

具体的には以下のような形式でテストを記述することができます。

    TestCase('testCase', {
        'test_testCase' : function () {
            // window.alertはこのfunction内でのみstub化する
            this.stub(window, 'alert');

            setTimeout(function () {
                window.alert('ok');
            });
            // this.clockが最初から定義されており、restoreも不要
            this.clock.tick(100);

            // window.alert.restore()不要
            assertCalledOnce(window.alert);
        }
    });
        if (condition) {
            ConditionalAsyncTestCase(name, condition, opt_proto);
            return;
        }
        AsyncTestCase(name, opt_proto);

conditionが指定されている場合はConditionalAsyncTestCaseを、指定されていない場合はAsyncTestCaseを呼び出します。

AsyncTestCaseはTestCaseの非同期実行版です。
TestCaseとの違いは「引数に非同期実行用の引数(queue)を渡すかどうか」なので、引数を無視した場合TestCaseとの違いはありません。

ConditionalAsyncTestCaseは第二引数に指定したfunctionがtrueを返した場合のみtest caseを実行します。

具体的には以下のように使用します。

    ConditionalAsyncTestCase('touch device only', function () {
        return 'ontouchstart' in window;
    }, {
        'test_touch' : function () {
            //...
        }
    });

なぜbooleanではなくfunctionを渡すかというと、JsTestDriverは高速化のためにテスト実行毎にJSの読み直しは行わず変更された部分のJSのみ差分で転送するため、*TestCase外の部分はブラウザで最初に読み込まれた段階でしか実行されません。
このため以下のようにtest caseの外部でfunctionの定義を変更すると、開発中に条件を変更しても処理が走らないので注意してください。

    // 最初に読み込まれた時にしか評価されない
    ('ontouchstart' in window) && AsyncTestCase('touch device only', {
        'test_touch' : function () {
            //...
        }
    });

function assert*

assert系を追加しています。

もっとうまく書く方法もあるかもしれませんが、とりあえず必要だったものだけ追加しています。


実際にはこれにIDEで補完するための記述を加えて使用しています。

JSのUnitTest関連技術

ざっくり以下のようなツールが関連する

CIサーバ系(Jenkins等)

何かのタイミングで自動的にテストを実行する場合に使用

「Swarm系」、「結合テスト系」を操作し、その結果を蓄積、報告する

結合テスト系

利点

  • IDEを使えばテストの定義が簡単
  • 実ブラウザでテストを実行するので検証が確実
  • 標準で画面遷移も含めた結合テストをサポート
  • htmlの切り出しが不要で実サービスを使ったテストが可能
  • CIサーバとの連携が可能

欠点

  • IDEを使ったテストは柔軟性に欠ける
  • 実ブラウザを使うので起動が遅い
  • 実ブラウザを使うので安定性に欠ける
  • テストがUI(html, css)に依存する
  • HeadLess系が使えない(多分)
  • ブラウザのみでテストが完結しない

ある程度UIが安定しているサービスに対してのサーバも含めたブラックボックステストに向く

Swarm系

利点

  • 実ブラウザでテストを実行するので検証が確実
  • UnitTest系のものが使えるのでライブラリ単位のテストが可能
  • CIサーバとの連携が可能
  • htmlの埋め込みを使ったUIの検証が可能
  • JSのキャッシュを使用した高速なテストが可能
  • コマンドラインからテストの実行が可能
  • スマートフォンでのテストが可能
  • HeadLess系が使える
  • (一部のものは)結合テスト的な動作も可能
  • (一部のものは)コードカバレッジ取得可能

欠点

  • 実ブラウザを使うので安定性にかける
  • イベント関連のテストが弱い
  • ブラウザのみでテストが完結しない
  • 実行速度がブラウザ環境に依存する

TDD、UnitTestがメインだが、結合テストも可能

HeadLess Browser系

利点

  • ブラウザの起動が無いので高速に実行できる
  • 結合テスト的な動作も可能
  • 実ブラウザでテストを実行するので検証が確実
  • UnitTest系のものが使えるのでライブラリ単位のテストが可能
  • CIサーバとの連携が可能
  • コマンドラインからテストの実行が可能
  • ブラウザのみでテストが完結する
  • (一部のものは)サーバサイド環境と相性がいい

欠点

  • テストできないブラウザがある
  • イベント関連のテストが弱い
  • (一部のものは)環境構築が大変

主にWebkit系でのTDD、UnitTestがメイン。スマートフォン環境、IE環境のテストはできない

JSエンジン系

利点

  • ブラウザの起動が無いので高速に実行できる
  • UnitTest系のものが使えるのでライブラリ単位のテストが可能
  • CIサーバとの連携が可能
  • コマンドラインからテストの実行が可能
  • クライアントのみでテストが完結する
  • サーバサイド環境と相性がいい

欠点

  • 環境構築が大変
  • テストできないブラウザがある
  • DOM、イベント、ブラウザ関連のテストが弱い
  • 結合テスト的な動作は別サポート

HeadLess系に近いが、純粋なJS環境なのでブラウザ関係のテストが難しい

UnitTest系

他言語環境でいうxUnit系にあたる。利点、欠点は一般的なxUnitに同じ

  • 安定のQUnit
  • 新進のJasmine
  • 革新のMocha

mock系

基本的にSinonJSでカバーできる。UnitTest系よりむしろこちらの使い方のほうが重要

JsTestDriverとphantomjsとJenkinsを使ってのJSの継続的なテスト

JsTestDriverとphantomjsとJenkinsを使ってのJSの継続的なテストを行う方法を解説します。

Javaのインストール

JsTestDriver、Jenkins共に実行にJavaが必要になるため、Javaのインストールを行いましょう。
すでにインストール済みの場合は必要ありません。 

JsTestDriverのインストール

  1. JsTestDriverのjarを落としましょう
  2. ダウンロードしたJsTestDriverを—portオプションで起動しましょう( $ java -jar JsTestDriver[バージョン番号].jar —port 9876 )
  3. 設定ファイルのサンプルをダウンロードしてJsTestDriver.jarと同じディレクトリにJsTestDriver.confの名前で保存しましょう

これでJsTestDriver serverが起動します。
今回はテスト対象としてphantomjsを使用しますが、他にテスト対象のブラウザがある場合、 http://[JsTestDriver server]:9876/capture へ接続して放置します。
(テスト毎にリロードを行う必要はありません) 

phantomjsとの連携

  1. 本家のドキュメントを見ながらphantomjsを入れます(基本的にダウンロードするだけ)
  2. phantomjsとjstestdriverをつなぐ用のJSをダウンロードします
  3. phantomjsにつなぐ用JSを渡して起動します( $ phantomjs phantomjs-jstd.js )

2.で配布されているjsは標準で9876 portに接続するので設定は不要です。
(9876 port以外に接続する場合、中に書かれているport番号を変更してください)

Jenkinsの設定

  1. 「新規ジョブ作成」から「フリースタイル・プロジェクトのビルド」を選択します
  2. 「ビルド」->「ビルド手順の追加」から「シェルの実行」を選択します
  3. シェルスクリプト」にJsTestDriverの実行コマンドを記述します(サンプルはこちらです
  4. 「ビルド後の処理」->「ビルド後の処理の追加」->「JUnitテスト結果の集計」を選択します
  5. 「ビルド後の処理」->「JUnitテスト結果の集計」->「テスト結果XML」にJsTestDriverの実行コマンドに記述した「[適当に空ディレクトリのパス]testOutput/」を入力します
  6. 「保存」を押せば完成です。

これでJsTestDriverとphantomjsとJenkinsを使ってのJSの継続的なテストが出来るようになりました。

OS依存の部分は書いていませんが、どれもほぼ「ダウンロードすれば使える」ものなので大丈夫だと思います。
(特にWindowsだとほんとにダウンロードして解凍してインストーラ走らせれば終わります) 

JsTestDriverのAsyncTestCase

この内容はJsTestDriver WikiのAsyncTestCaseを意訳したものではありません。
http://code.google.com/p/js-test-driver/wiki/AsyncTestCase

TestCaseはAsyncTestCaseの同期実行版なので基本的にAsyncTestCaseを使いましょう。
(TestCaseをAsyncTestCaseに置き換えるだけでそのまま実行できます)

assert関係はこちらからどうぞ
Assertions http://code.google.com/p/js-test-driver/wiki/Assertions

以下の二つはTestCaseと互換の基本的な実行方法です。
オブジェクトリテラルを渡す方法
    AsyncTestCase(‘testCaseName’, {
        ‘setUp’ : function () {},
        ‘testNameA’ : function () {},
        ‘testNameB’ : function () {},
        ‘tearDown’ : function () {}
    });
.prototypeを拡張する方法
    var MyTestCaseName = AsyncTestCase(‘testCaseName’);
    MyTestCaseName.prototype.setUp = function () {};
    MyTestCaseName.prototype.testNameA = function () {};
    MyTestCaseName.prototype.testNameB = function () {};
    MyTestCaseName.prototype.tearDown = function () {};
両者に大きな違いはありませんが、特にオブジェクトリテラルを渡す場合ではテスト名に記号等を使用するとコマンドラインからテストを指定して実行する場合に問題が発生する可能性があるので注意してください。
(コマンドラインから指定しない場合、スペース等の記号を含めても問題ありません)
JsTestDriverのコマンドラインオプション http://0-9.tumblr.com/post/16860918312/jstestdriver-commandlineflags
テスト名は常に「test」から始まっている必要があります。
(setUp、tearDown以外の「test」から始まっていないメソッドは実行されません)
ただし、「test」から始まっていないメソッドも「this.メソッド名」で呼び出すことが可能です。
各テストメソッドは毎回以下のサイクルで実行されます。
    setUp -> テストメソッド -> tearDown
各テストメソッドはアルファベット順にソートされて実行されるため、上記の例の場合以下の順番で実行されます。
    setUp -> testNameA -> tearDown -> setUp -> testNameB -> tearDown
各メソッド内のthisは共有されており、「setUp -> テストメソッド -> tearDown」の実行サイクル中以下のように保持されます。
    setUp -> テストメソッド -> tearDown -> (thisを破棄) -> setUp -> テストメソッド -> tearDown

以下はAsyncTestCase独自の方法です。

queueを使った非同期テスト
    AsyncTestCase(‘testCaseName’, {
        ’testNameHoge’ : function (queue) {
            var state = 0;
            assertEquals(0, state);
            queue.call(function(callbacks) {
                state++;
                assertEquals(1, state);
                setTimeout(callbacks.add(function () {
                    state++;
                    assertEquals(2, state);
                }), 500);
            });
            queue.call(function(callbacks) {
                state++;
                assertEquals(3, state);
                setTimeout(callbacks.add(function () {
                    state++;
                    assertEquals(4, state);
                }), 200);
            });
        }
    });

AsyncTestCaseはテストメソッドの第一引数にqueueオブジェクトを渡すので、そのqueueオブジェクトの.callメソッドにcallbackをわたし、そのcallbackの第一引数に渡されるcallbacksオブジェクトの.addメソッドにcallbackを渡すことで非同期の実行ができます。

実行順は上記のテストにもありますが、以下のような順番になります。

テストメソッド全体 -> (遅延) -> queue.call -> callbacks.add -> queue.call -> callbacks.add

各queue.call内のcallbackは遅延実行され、先に追加されているqueue.call内のcallbacks.addが全て処理されるまで実行されません。
    AsyncTestCase(‘testCaseName’, {
        ’testNameHoge’ : function (queue) {
            var state = 0;
            assertEquals(0, state);
            queue.call(function(callbacks) {
                state++;
                assertEquals(1, state);
            });
            assertEquals(0, state);
        }
    });

テストメソッド全体、queue.call、callbacks.add内のthisはすべて同じものとなります。
    AsyncTestCase(‘testCaseName’, {
        ’testNameHoge’ : function (queue) {
            assertNotEquals(1, this.hoge);
            this.hoge = 1;
            queue.call(function(callbacks) {
                assertEquals(1, this.hoge);
                setTimeout(callbacks.add(function () {
                    assertEquals(1, this.hoge);
                }));
            });
        }
    });

htmlのテスト

JsTestDriverのテストはサーバに接続しているブラウザ環境上で実行されるため、通常のDOM APIを使ったhtmlの組み立ても可能ですが、各テストコード内に以下のようなJavaScriptのコメント形式でhtmlを記述することにより簡単にテスト用htmlを埋め込むことができます。
    AsyncTestCase(‘testCaseName’, {
        ‘setUp’ : function () {
            /*:DOC foo = <div><p>foo</p></div>*/
            /*:DOC += <div id=”foo”></div> */
        },
        ‘testNameHoge’ : function () {
            /*:DOC foo = <div><p>foo</p></div>*/
            /*:DOC += <div id=”foo”></div> */
        }
    });
setUp内で設定したhtmlは各テストメソッドから参照可能です。
(ただし、htmlへの変更はtearDown実行後に破棄されます)
テストメソッド内で設定したhtmlはtearDown実行後に破棄されます。
:DOCの後に変数名を置く形式「:DOC foo = 」の場合、各コード内からはthis.fooで参照できます。
:DOCの後に+=を置く形式「:DOC += 」の場合、document.bodyにappendChildされます。
各htmlは要素を並列においても最初のノード以外を無視します。
以下の例ではfooのみが追加され、barは無視されます)
    /*:DOC +=
        <div id=”foo”></div>
        <div id=”bar”></div>
    */
各コードは記述されている部分がhtmlを生成するJSに変換されて実行されるため、テストメソッドの任意の行に記述できますが、記述された行以前からは参照できません。
    AsyncTestCase(‘testCaseName’, {
        ‘testNameHoge’ : function () {
            // この時点ではundefined
            console.log(this.foo);
            /*:DOC foo = <div><p>foo</p></div>*/
            // ここでは[HTML Element]
            console.log(this.foo);
        }
    });


注意点

クライアントに限って、延々サーバに繋げてると動きがおかしくなることがあります。
(社内では共有のサーバにiPhone, Androidをつなげて放置してますが、週一回か二回くらい再起動してます)

console.logはブラウザ上に存在しない場合もあるし、つかいまくるとブラウザが不安定になったりするので、コンソールに値出したいだけならjstestdriver.console.logを使いましょう。
(ただし、console.log = jstestdriver.console.logとかしても動かないので注意)

一応debugger statementも動きます。ただし、timeout秒以上経つとそこで死ぬので注意。
(timeout秒以内ならstartしなおせば動く)

TestCase、AsyncTestCase外の実行場所はテストを再実行した場合には再実行されません。
(初期化コードとかは基本setUpに書きましょう)

JsTestDriverのコマンドラインオプション

この内容はJsTestDriver WikiのCommandLineFlagsを意訳したものです。
http://code.google.com/p/js-test-driver/wiki/CommandLineFlags

$ java -jar JsTestDriver.jar —help
 —browser VAR             : The path to the browser executable
 —browserTimeout VAR      : The ms before a browser is declared dead.
 —captureAddress VAL      : The address to capture the browser.
 —captureConsole          : Capture the console (if possible) from the browser
 —config VAL              : Loads the configuration file
 —dryRunFor VAR           : Outputs the number of tests that are going to be
                             run as well as their names for a set of
                             expressions or all to see all the tests
 —help                    : Help
 —port N                  : The port on which to start the JsTestDriver server
 —preloadFiles            : Preload the js files
 —requiredBrowsers VAR    : Browsers that all actions must be run on.
 —reset                   : Resets the runner
 —server VAL              : The server to which to send the command
 —serverHandlerPrefix VAL : Whether the handlers will be prefixed with jstd
 —sslPort N               : The SSL port on which to start the JsTestDriver
                             server
 —testOutput VAL          : A directory to which serialize the results of the
                             tests as XML
 —tests VAR               : Run the tests specified in the form testCase.testNa
                             me
 —verbose                 : Displays more information during a run

 —plugins VAL[,VAL]       : Comma separated list of paths to plugin jars.
 —config VAL              : Path to configuration file.
 —basePath VAL            : Override the base path in the configuration file. Defaults to the parent directory of the configuration file.
 —runnerMode VAL          : The configuration of the logging and frequency that the runner reports actions: DEBUG, DEBUG_NO_TRACE, DEBUG_OBSERVE, PROFILE, QUIET (default), INFO


サーバオプション

以下のオプションはJsTestDriverがサーバとして起動される場合に認識されるオプションです。

—port

サーバとして起動する際のポート番号です。
このオプションが指定された場合、JsTestDriverはサーバとして起動します。

ステータス

サーバとして起動しているJsTestDriverに対して「http://{サーバ名 or IP}:{ポート番号}/」で接続すると、現在そのサーバに接続しているブラウザのステータスが表示されます。

テスト対象ブラウザの追加

サーバとして起動しているJsTestDriverに対して「http://{サーバ名 or IP}:{ポート番号}/capture」で接続すると、接続したブラウザがテスト対象のブラウザとして追加されます。
接続したブラウザはキャプチャページを表示したまま更新しないでください。
また、最近のブラウザは非表示タブの動作を遅くする機能を持つものが多いので、タブの切替も行わないようにしてください。
(別Windowで立ち上げることを推奨します)
テスト対象のブラウザはそのサーバに接続できるものであれば同じ機体である必要はありません。
(JSが動作する環境であればiOS, Android等でも実行できます)
デバッグツールは使用出来ますが、debuggerステートメント等で動作を停止した場合でも設定ファイルのtimeout秒が経過した時点でタイムアウトとみなされるので注意してください。
JsTestDriverの設定ファイル書式 http://0-9.tumblr.com/post/16858820234/jstestdriver-configurationfile

—browser

ブラウザの実行ファイルのパスを,区切りで指定するとサーバの起動と同時にブラウザを起動します。
これはCI等の自動テストを行う場合にも使うことができます。
継続的ビルド http://code.google.com/p/js-test-driver/wiki/ContinuousBuild

クライアントオプション

以下のオプションはJsTestDriverがクライアントとして起動される場合に認識されるオプションです。

—captureConsole

以下のメソッドの出力内容をJsTestDriverクライアントを実行しているコンソールへも出力します。
console.log、console.debug、console.info、console.warn、console.error
ブラウザ標準のconsoleを使用しない場合(ブラウザ標準のconsole objectが存在しない場合、ブラウザ標準のconsoleへ出力したくない場合)はjstestdriver以下のメソッドを使用してください。

jstestdriver.console.log
出力はテキストのみ可能です。
各メソッドは出力時の先頭に[LOG]、[DEBUG]、[INFO]がつくことで区別されます。
console.debugは行番号を出力しません。

—config

設定ファイルの参照先を変更します。
設定ファイルは標準ではカレントディレクトリのjsTestDriver.confを参照します。
JsTestDriverの設定ファイル書式 http://0-9.tumblr.com/post/16858820234/jstestdriver-configurationfile

—reset

テスト対象のブラウザのテストを実行している領域ををリロードします。
JsTestDriverは標準でJSの再読み込みを行わないため、広域変数を使用している場合「—reset」オプションを指定して実行毎に環境をクリアしてください。

—server

JsTestDriverサーバが実行されているURLを指定します。
設定ファイルの値を上書きします。
JsTestDriverの設定ファイル書式 http://0-9.tumblr.com/post/16858820234/jstestdriver-configurationfile
末尾に「/」をつけないように注意してください。

—tests

実行するテストを指定します。

・all すべてのテストを実行します。
・TestCaseName TestCase(‘TestCaseName’, {})で指定されたテストを実行します
・TestCaseName.testName TestCase(‘TestCaseName’, { ‘testName’ : function () {} })で指定されたテストを実行します

JsTestDriverの設定ファイル書式

この内容はJsTestDriver WikiのConfigurationFileを意訳したものです。
http://code.google.com/p/js-test-driver/wiki/ConfigurationFile

JsTestDriverの設定ファイルはYAMLで書きます。
設定ファイルはテストで使用するファイルをJsTestDriverが読み込むために使用され、標準はJsTestDriverコマンドを実行したカレントディレクトリの「jsTestDriver.conf」を参照します。
この参照先はコマンドラインオプションの「—config」から変更することができます。

各ファイルのベースパスはjsTestDriver.confと同じディレクトリとなりますが、コマンドラインオプションの「—basePath」から変更することができます。また、設定ファイル内のbasepathでも変更できます。
それ以外にも、URLを記述するとそのURLからファイルをダウンロードしてテストを実行しますが、safariに関しては読み込みに失敗するので基本的にはファイルパスを書きましょう(バグ?)

サンプル
    server: http://localhost:4224

     basepath : ../

    load:
      - src/*.js

    test:
      - src-test/*.js

    exclude:
     - uselessfile.js

    serve:
     - css/main.css

    proxy:
     - {matcher: “*”, server: “http://localhost/whatever”}

    plugin:
     - name: “coverage”
       jar: “lib/jstestdriver/coverage.jar”
       module: “com.google.jstestdriver.coverage.CoverageModule”

    timeout: 90

各項目に対する説明は以下のとおりです。

server:

接続先のサーバを指定します。この値はコマンドラインの「—server」オプションで上書きすることができます。
末尾に「/」を含めないように注意してください。
コマンドラインオプション http://code.google.com/p/js-test-driver/wiki/CommandLineFlags

basepath:

各種ファイルパスの基本ディレクトリを変更できます。basepath自体を相対値で書いた場合、jsTestDriver.confからの相対値になります。

load:

テストを実行する前に読み込むファイルを指定します。
ファイル名に*を使うことで複数ファイルを一度に指定することが可能です。
(*を使用した場合ファイルはアルファベット順に読み込まれます)
ファイル名をhttpから記述することで、外部ファイルを読み込むことが可能です。
同じファイルは複数回指定しても一度しか読み込まれないため、以下のような記述を行うことが可能です。

    load:
     - src/lib/jquery.js # 基本ライブラリは先に読み込む
     - src/lib/* #jquery.jsは先に読み込まれているためここでは読み込まれない

test:

テスト対象のファイルを指定します。
test:で指定されたファイルはload:で指定されたファイルの後に読み込まれます。
test:ではhttpからの外部ファイルを指定できません。

exclude:

除外ファイルを指定します。
主に読み込みファイルを*で指定した場合に使用します。

    load:
     - src/lib/*
    exclude:
     - src/lib/debug.js #src/lib/*内からsrc/lib/debug.jsのみ除外

serve:

画像、css、htmlなどの静的ファイルをテストファイルと同じドメイン上に読み込みます。
読み込んだファイルは/test/以下でファイルパスと共に参照できるようになります。

proxy:

プロキシとして動作するようにJsTestDriverを設定します。
Proxy設定 http://code.google.com/p/js-test-driver/wiki/Proxy

plugin:

JsTestDriverのプラグインを読み込みます。
プラグイン設定 http://code.google.com/p/js-test-driver/wiki/Plugins

timeout:

テストを実行する際のタイムアウト時間を設定します。
初期値は90秒です。

JSのイベント関係で重要そうなURLをいくつか

http://d.hatena.ne.jp/language_and_engineering/20090907/p1
>JavaScriptの動かないコード (中級編) clickイベントを強制的に発生させたい (fireEvent/createEventの使い方) - 主に言語とシステム開発に関して

JSのイベントをコード内から発火する方法

http://d.hatena.ne.jp/koumiya/20080916/1221580149
>ブラウザの戻るボタンで戻ったときに呼ばれるイベントとかキャッシュとかそこらへんのこと - koumiyaの日記

ブラウザによっては「histry.backでonloadが呼ばれない問題」があることと、その回避策に関して

http://www.quirksmode.org/dom/events/index.html
http://www.w3.org/wiki/List_of_events

JSで定義されてるイベント一覧(全部じゃないかも)

http://jsdo.it/kyo_ago/log_event

上記で定義されてるイベントを:textに貼ったコード。
event log等がない環境でどういうイベントが呼ばれてるか監視したい場合に