qunit-tapを使ってnode.jsのテストをproveで行う

Shibuya.js テクニカルトーク Test.jsid:t-wadaさんの話がとても興味深く、
js テスト放浪記
qunit-tapというのが魅力的に感じたのでnode.jsのテストで使ってみました。
GitHub - twada/qunit-tap: A TAP Output Producer Plugin for QUnit

quinit-tapとは

まずQUnitについて。
QUnit
QUnit自体は全然使ったことなくて詳しくは知らないのですが、もともとjQueryのテスト用に作られたテスティングフレームワークで、今はjQueryに関係なく色々な場面でのJavaScriptのテストに使えるようになっている、とのこと。

QUnit is a powerful, easy-to-use, JavaScript test suite. It's used by the jQuery project to test its code and plugins but is capable of testing any generic JavaScript code (and even capable of testing JavaScript code on the server-side).


node.js用にもnode-qunitというのがあってコマンドラインQUnit的なテストを行うモジュールがあるのだけど、qunit-tapはそれとは関係なくQUnitの出力形式をTAP形式に揃える、という目的で作られています。TAP出力については若干古いかもしれないけど下記URLなどを参照。
404 Blog Not Found:「同じコード」の同じって何さ - TAPのススメ
第1回 Perlにおけるテストの概要/TAPとは?:Happy Testing Perl|gihyo.jp … 技術評論社
TAP形式だと何が嬉しいかというと、PerlのTest::Harnessモジュールに付属するproveコマンドでテスト実行方法や出力をカスタマイズできるのです。
http://search.cpan.org/~andya/Test-Harness-3.23/bin/prove
例えば、stateを保存して前回失敗したテストだけを次回実行するようにする、とか
proveをうまく使ってテスト実行を効率化しよう - JPerl Advent Calendar 2010 Casual Track
複数のテストを並列で走らせたり、Pluginを書けば終了後に結果をGrowl通知させてくれたりとかもできます。

インストール

2010/03/25時点での最新版は0.9.8。npmで簡単に入ります。

$ npm install qunit-tap
npm info it worked if it ends with ok
npm info using npm@0.3.17
npm info using node@v0.4.3
npm info preinstall qunit-tap@0.9.8
npm info install qunit-tap@0.9.8
npm info postinstall qunit-tap@0.9.8
npm info preactivate qunit-tap@0.9.8
npm info activate qunit-tap@0.9.8
npm info postactivate qunit-tap@0.9.8
npm info build Success: qunit-tap@0.9.8
npm ok

テストの準備をする

qunit-tap自体は出力を整える役目をするだけなので、QUnit本体は別途持ってくる必要があります(qunit-tapのnpmパッケージにも含まれているけれど)。

$ mkdir myproject
$ cd myproject
$ mkdir deps
$ git clone git://github.com/jquery/qunit.git deps
$ ls deps/
README.md    package.json qunit        test

で、qunit-tapを使う設定をするヘルパースクリプト(test_helper.js)を用意します。

exports = module.exports = global;

QUnit = require("./deps/qunit/qunit").QUnit;
var qunitTap = require("qunit-tap").qunitTap;
qunitTap(QUnit, require("sys").puts, { noPlan: true });

QUnit.init();
exports.assert = QUnit;

nodeで使うならこれだけで十分です。cloneしてきたqunitをrequireしてから、読み込まれたQUnitオブジェクト、標準出力関数、オプション、を引数に渡してqunitTap()を呼んで、諸々exportsに入れてやります。

テストを書く

準備ができたのでテストを書いてみます。proveコマンドはデフォルトでは"t"ディレクトリ以下のファイルをテスト対象として探索するので、一応./t以下に作ります。

require('../test_helper.js');

QUnit.test('hoge', function() {
    assert.ok(1);
});

QUnit.start();

作成したテストはnodeコマンドで実行することでテストが走ります。

$ mkdir t
$ vi t/hoge.js
$ ls
deps           t              test_helper.js
$ ls t/
hoge.js
$ node t/hoge.js
# test: hoge
ok 1
1..1

無事にテストが実行され、'ok'とTAP形式のテスト結果が出力されました。調子に乗ってもう一つテストを作ってみます。

require('../test_helper.js');

QUnit.test('fuga', function() {
    assert.expect(2);

    assert.equal(1 + 23, 24,          'add');
    assert.equal('fuga', 'fu' + 'ga', 'cat');
});

QUnit.test('piyo', function() {
    assert.expect(3);

    var a = [];
    assert.deepEqual(a, [],       'empty');
    a.push('hoge');
    assert.deepEqual(a, ['hoge'], 'pushed');
    a.push({ key: 'value' });
    assert.deepEqual(a, [
        'hoge',
        { key: 'value' }
    ], 'has Object');
});

QUnit.start();
$ node t/fuga.js
# test: fuga
ok 1 - add
ok 2 - cat
# test: piyo
ok 3 - empty
ok 4 - pushed
ok 5 - has Object
1..5

うまくいきますね。テストが通らない場合は下記例のように"not ok"が出力されます。

$ node t/fuga.js
# test: fuga
not ok 1 - add, expected: 25 result: 24
ok 2 - cat
# test: piyo
ok 3 - empty
ok 4 - pushed
ok 5 - has Object
1..5

proveコマンドを使う

さて 複数のテストファイルがあるといちいち実行するのも面倒になってきます。ここでproveコマンドの登場。
デフォルトでは".t"の拡張子を持つファイルをperlで実行するようになっているので、そのへんはオプションで調整してやります。

$ prove --ext=.js --exec=node
t/fuga.js .. ok
t/hoge.js .. ok
All tests successful.
Files=2, Tests=6,  1 wallclock secs ( 0.03 usr  0.01 sys +  0.12 cusr  0.02 csys =  0.18 CPU)
Result: PASS
$ prove --ext=.js --exec=node -v
t/fuga.js ..
# test: fuga
ok 1 - add
ok 2 - cat
# test: piyo
ok 3 - empty
ok 4 - pushed
ok 5 - has Object
1..5
ok
t/hoge.js ..
# test: hoge
ok 1
1..1
ok
All tests successful.
Files=2, Tests=6,  0 wallclock secs ( 0.03 usr  0.01 sys +  0.12 cusr  0.02 csys =  0.18 CPU)
Result: PASS

こんなかんじで、テスト対象ファイルを勝手に見つけ出して実行し、結果を集計してくれます。"-v"オプションで結果を詳しく表示。
わざわざ"--ext"や"--exec"を指定するのが面倒であれば、".proverc"という設定ファイルを用意しておけばそこに記述されたオプションが使用されるようになります。

$ cat ./.proverc
--exec=/Users/sugyan/.nvm/v0.4.3/bin/node
--ext=.js
$ prove
t/fuga.js .. ok
t/hoge.js .. ok
All tests successful.
Files=2, Tests=6,  1 wallclock secs ( 0.02 usr  0.01 sys +  0.13 cusr  0.02 csys =  0.18 CPU)
Result: PASS

これでproveを使ってスムーズに開発を進めることができるようになりそうです。

おまけ

上記の例ではqunitgithubからcloneして持ってきたけど、qunit-tapのnpmパッケージにも入っているのでわざわざ落としてこなくてもパスを辿ればqunitのロードができます。

exports = module.exports = global;

var path = require('path');
QUnit = require(path.join(path.dirname(require.resolve('qunit-tap')), '..', 'vendor', 'qunit', 'qunit', 'qunit')).QUnit;
require("qunit-tap").qunitTap(QUnit, require("sys").puts, { noPlan: true });

QUnit.init();
exports.assert = QUnit;

こんなかんじでtest_helper.jsを用意してもたぶん大丈夫。

参考URL、謝辞

QUnit-TAP : JavaScript のテスティングフレームワークQUnitからTAP出力する - t-wadaの日記
id:t-wadaさんにはnode.jsでqunit-tapを使うにあたり色々と相談に乗っていただき より使いやすいように調整していただいたり 本当にお世話になりました。ありがとうございました!!