JavaScript Advent Calendar 2011 (オレ標準コース) : ATND の 5日目の記事です。

最近書いた、スマートフォン向けのJavaScriptライブラリ?(紙芝居のようなものを描画するライブラリ)を書いている時に、テストをどうしようかといろいろ考えていましたが、RequireJS + QUnit + QUnit-TAP + PhantomJS という構成で書くことにしました。
(Jasmineが好きだったのでpavlovMochaも気になっていましたが、上記の構成が安定しそうと思った)
RequireJSが入っているのは、そのライブラリはRequireJSでモジュールを書いていて、r.jsでビルドするような感じにしている為です。

この構成を説明するにあたって、AMDTestingというサンプルプロジェクトを作成したので、それを見ながら書いていきます。

DomModule js   ~ Dropbox workspace toybox AMDTesting  JetBrains WebStorm  WebStorm WS 110 518

シンプルなプロジェクト(AMDTesting)の構成

用意するライブラリとしては以下の2つです。

PhantomJSDownloadするなり$ brew install phantomjsするなりしてインストールしておきます。

QUnit-TAPは以下のようにsubmoduleとしてプロジェクトに追加しています。(アップデート対応とか考えるとコチラのほうがよさそうなので)

➜  AMDTesting ✗ git submodule add git://github.com/twada/qunit-tap.git test/lib/qunit-tap
➜  AMDTesting ✗ cd test/lib/qunit-tap
➜  qunit-tap ✗ git submodule update --init
# QUnit-TapのsubmoduleにQUnitなどが含まれている

qunit-tapのexampleからrunnerとなるphantomjs_test.shとrun_qunit.jsを取り出してtest直下に置いています。
同様にtest直下にQUnitを実際に走らせるページとして以下のような内容のindex.htmlを置きます(ファイル名自体は phantomjs_test.sh に合わせる)

index.htmlのbody以下はQUnitのテンプレート、”requireJSを読み込む”部分より上はQUnit-TAPのテンプレートになっているのであんまり気にしなくても大丈夫です。requireJSで書いたモジュールをテストするのに必要な構成を書いている部分はその間の部分で、
モジュールのパスを楽に書けるように、baseUrl:’spec’でベースとなるパスをspecディレクトリにしています。
また、’src’:’../../src’とすることで、モジュールパスにsrc/hogeと書くと../../src/hogeとなるようにしています。

そして、それぞれのモジュールのテストをモジュールとしてdefineし、index.htmlでまとめて読み込んでいます。
テストする量が多くなったらグループ分けするようなモジュールを作るとイイきがします

AMD形式のモジュールのテストするモジュールについて

例えば、以下のようなモジュールを定義して

// sampleモジュール
define(function () {
    var module = {};
    module.getCharacterAt = function (str, pos) {
        return str.substr(pos - 1, 1);
    }
    return module;
});

次のようにテストするモジュールを書いて、テストコードを追加していきます。

// テスト対象をsrcから読み込んでテストコードを書く
define(["src/sample"], function (module) {
    test("getCharacterAt", function () {
        var str = "STRING";
        equal(module.getCharacterAt(str, 1), "S");
        equal(module.getCharacterAt("STRING", 3), "R");
        equal(module.getCharacterAt(str, str.length), "G");
    });
});

そして、テストを走らせる時はテストモジュールの方を読みこめば、requireJSが依存関係を解決(src/sampleの方をテストモジュール内に読み込んで)してテストを実行できます。

DOMを扱うモジュールのテスト

ここまで触れていませんでしたが、QUnit-TAPのsampleにはPhantomJSに対応したサンプルが含まれいます。
sampleからコピーしてきたphantomjs_test.shというのがまさにそれで、index.htmlを PhantomJS を使って実行します。
PhantomJSはGUIがないWebkitベースのブラウザをコマンドライン動かすようなツールで、DOMを扱ったヘッドレステストも行う事ができます。

今までCUIで完結するテストはEnvjsのようにDOMをエミュレートするか、本物のブラウザを立ち上げて裏で実行させるかのような方法をとっていましたが、PhantomJSはそれがもう少し手軽にできるようになった感じです。(Webkit系の動作だけしかテストできないという欠点もありますが)

次のようなDOMに依存したモジュールのテストも行う事ができます。
まず以下のようなDOMに依存するモジュールがあるとします。


// domModule
define(function () {
    var module = {};
    module.append = function (parentNode, elem) {
        if (elem.nodeType === 1) {
            parentNode.appendChild(elem);
        }
    }
    return module;
});

そして、これをテストするモジュールを作成します。

define(["src/domModule"], function (module) {
    // #qunit-fixture" はreset時に初期化される
    test("append", function () {
        var fixture = document.getElementById("qunit-fixture");
        var addElem = document.createElement("div");
        module.append(fixture, addElem);
        equal(fixture.firstElementChild.nodeName, "DIV");
    });
    test("append-2", function () {
        var fixture = document.getElementById("qunit-fixture");
        var addElem = document.createElement("a");
        module.append(fixture, addElem);
        equal(fixture.firstElementChild.nodeName, "A");
    });
});

QUnitのテンプレートにはid=”qunit-fixture”の空divがありますが、この領域はtest()を実行する度に初期値の内容に書き換えられるようになっているため、htmlの断片などのfixtureを入れる場所として利用できます。
ブラウザでアクセスすれば、上記のテストが実行できるのが通るのがわかると思います。
file:///だと実行できない場合もあるので、

➜  AMDTesting git:(master) ✗ python -m SimpleHTTPServer 8000

という感じで、localhostを立ててブラウザで http://localhost:8000/test/ にアクセスという形をよくとっています。
話を戻して、 PhantomJSを使った場合は、phantomjs_test.shを実行するとブラウザでindex.htmlにアクセスしたのと同じように、コマンドラインからテストが実行できます。

QUnit-TAP

QUnit-TAP : JavaScript のテスティングフレームワークQUnitからTAP出力する – t-wadaの日記

QUnit-TAPはTAP形式のテスト結果を出してくれるため、proveコマンドでテストを実行できます。

➜  test git:(master) ✗ prove -o phantomjs_test.sh

proveでテストを実行できるとCIで回すのが簡単に行えたり、Perlのテスト資産が利用できたり、失敗したテストだけを実行するなどといったことができるようになるので、TAP形式の出力はいろいろと発展性があると思います。

あまり小難しいこと考えなくても、テスト結果がシンプルにわかるから使うとかでもいい気がします。

参考

最初からRequireJSを使うことは決めていて、後からAMDモジュールのテストはどう書くのがいいのかを模索し始めた時に、
以下はAMDモジュールのテスト構成を考える上でとても参考になりました。

Post Navigation