Google が開発した JavaScript 用テストフレームワーク Google JS Test を使う

JavaScript のテストを書きたくなって調べていたら、2011年9月29日に公開されたばかりGoogle JS Test を見つけた。

これは Google 社内で使われている JavaScript のテスト環境らしく、同じく同社が開発している V8 エンジンの上で動作する。
DOM 操作はできないのだけれど、後述するように、Google は DOM へのアクセスを隔離することでテストが可能になるし、コードが簡潔になる、という理由から特に問題視はしていないようだ。
インストールも簡単だったし、JS のテストを書きたい人は使ってみるといいかも知れない。

インストール

Mac ユーザで homebrew をいれているなら

% brew update
% brew install google-js-test

だけで入る。
DebianLinux なら apt-get でインストールできるらしい。

チュートリアル

英語が読むのが面倒な人はリンク先の最初のコードを some_functions.js、2個目のコードを namespace.js、3個目のコードを some_functions_test.js という名前で保存して

gjstest --js_files=namespace.js,some_functions.js,some_functions_test.js

を実行する。この例から分かるように、js_files オプションで指定された順番に JavaScriptソースコードが読み込まれる。
最後に指定したsome_functions_test.js の中に記述されたテストの実行結果がターミナルに表示されるはず。
エラー文を見れば、12行目のテストで [ 'Hello', 'world' ] が期待されているところに [ 'Hello', 'World' ] が返されていることとか、64行目のテストで TypeError で始まり must be a number で終わる文字列がエラーとして投げられることを期待されているところでエラーが発生していないことが分かる。
結局、僕はこんな感じにデバッグした。

// Return some interesting words.
myproject.getSomeWords = function() {
    return ['Hello', 'world'];
};

// Add the supplied numbers and then call back with the result.
myproject.addNumbersAndCallBack = function(a, b, resultCallback) {
    if (typeof a === 'string') {
        throw 'TypeError first argument must be a number';
    }
    resultCallback(a + b);
};

HTML出力

テスト結果を HTML 形式で出力することもできる

gjstest --js_files=namespace.js,some_functions.js,some_functions_test.js --html_output_file=test_result.html

こうすることで test_result.html というファイルが出力される。
ブラウザで見るとこんな感じで表示される。
f:id:yuku_t:20111101130706p:image
サーバで定期実行させて、その結果をブラウザから見れるのは便利そう。

テスト用関数

チュートリアル中で使っているのは、expectThatexpectEqexpectCall
他にも使いそうなのはだいたい揃っているし、自分で新しい関数を定義することもできる。

モック

callback 関数が正しい引数で呼び出されているかどうかをテストするには、上のチュートリアル中でもやっているように createMockFunction で作ったモックオブジェクトと、expectCall を組み合わせて使う。

DOM操作をテストする

JavaScript のテストをするのに障壁になるのが、DOM や setTimeoutAjax が絡むような場合。
Google JS Test のドキュメントには、DOM 操作のテストをしたい場合、windowdocument を関数から追い出せば、前述のモックを使ってテストを行うことができる、と主張している。
例で示すと、エラー文を表示する以下のような関数があるとする。

function writeErrorMessage(msg, elementName) {
  // Write out the error message.
  document.getElementsByName(elementName).innerText = 'Error: ' + msg;
}

writeErrorMessage 関数内で DOM 操作を行なっている。この例に対し、実際に elementName のテキストに変化したかどうかをテストするには、テスト環境で DOM をエミュレートしなければならないが、そんな機能は Google JS Test にはない。
でも、この関数を次のように変形させれば、テストをすることができる。

function writeErrorMessage(msg, element) {
  // Write out the error message.
  element.innerText = 'Error: ' + msg;
}

今までは

writeErrorMessage('msg', 'hoge');

のように実行していたのが

var elem = document.getElementsByName('hoge');
writeErrorMessage('msg', elem);

のようにインタフェースが変更されてるので、関数内で elem に対して innerText プロパティが変更されているかどうかを、例えばこんな感じでテストすることができる。

function WriteErrorMessageTest() {}
registerTestSuite(WriteErrorMessageTest);

WriteErrorMessageTest.prototype.InnerText = function () {
    var msg = 'msg',
        elem = {};
    writeErrorMessage(msg, elem);
    expectEq(elem.innerText,  'Error: ' + msg);
};

Google の主張によれば、こうする方が関数もシンプルになるし、テストもシンプルになるからいいのだと。
setTimeout のテストも同様のテクニックを使うことで記述可能らしい。

おわりに

Google が最近公開した JavaScript 用テストフレームワーク Google JS Test を使ってみた。
僕は使ったことないのだけど、Google が開発している C++ 用のテストフレームワーク GTest と使い方が似ているらしく、ドキュメント中でGTestに関する知識が役に立つかも、というようなことが書いてあった。
C++ を書く際は GTest も使ってみたい。