QUnit + QUnit-TAPでJSのTDDをしてみた
JavaScriptでTDDするにはどうすればいいのかな・・・と思い、TDDBCではQUnit + QUnit-TAPが使われていたようなので、とりあえずその環境を整えてみました。
普段rspecを使っている自分としてはrspecっぽく書けるJasmineも気になっているのですが、とりあえずはQUnitを。
インストール
QUnit-TAPはnpmで提供されているので、それで入れるのが簡単そう。globalに入れることもできますが、とりあえずカレントディレクトリのnode_module/に入る以下のコマンドでインストール。
$ npm install qunit-tap
設定
まずは設定とか必要なモジュールのrequire等を行うtest_helperを作ります。
QUnit-TAPにサンプルでついてくるtest_helper.jsをほぼそのまま、パスのあたりだけ調節して使っています。
exports = module.exports = global;
var tryRequireThese = function() {
var args = Array.prototype.slice.apply(arguments);
for(var i=0; i < args.length; i+=1) {
try {
return require(args[i]);
} catch(e) {
// ignore
}
}
throw new Error("cannot find moduele: " + args);
};
QUnit = require('qunit-tap/vendor/qunit/qunit/qunit.js').QUnit;
var qunitTap = require('qunit-tap').qunitTap;
var sys = tryRequireThese("sys", "system");
for (var i in sys) exports[i] = sys[i];
puts = (typeof sys.puts === 'function') ? sys.puts : sys.print;
qunitTap(QUnit, puts, {noPlan: true});
QUnit.init();
QUnit.config.updateRate = 0;
exports.assert = QUnit;
テストを書いてみる
このへん利点がよくわかっていないのですが、TAPという形式での出力はproveというperl製のコマンドを使って実行するのが便利らしいので、それに従っておこうと思います。
すると、t/というディレクトリ以下にテストファイルを置いておくとproveコマンドが自動的にそのディレクトリ以下のファイルを自動的にテストファイルとして扱ってくれるらしいので、従います。
t/array_test.js
require('../test_helper.js');
QUnit.module('Module Array');
QUnit.test('#length', function() {
var ary = new Array(5);
assert.equal(ary.length, 5);
});
QUnit.test('#push', function() {
var ary = new Array();
ary.push('first');
assert.equal(ary.length, 1);
ary.push(2);
assert.equal(ary.length, 2);
});
QUnit.test('#concat', function() {
var ary1 = [1, 2, 3];
var ary2 = [4, 5, 6];
var res = ary1.concat(ary2);
// assert.equal(res, [1, 2, 3, 4, 5, 6]); # => fail
assert.deepEqual(res, [1, 2, 3, 4, 5, 6]);
});
QUnit.start();
実行してみます。
$ prove -vc --exec=node --ext=.js
t/array_test.js ..
# module: Module Array
# test: #length
ok 1
# test: #push
ok 2
ok 3
# test: #concat
ok 4
1..4
ok
All tests successful.
Files=1, Tests=4, 0 wallclock secs ( 0.04 usr 0.00 sys + 0.12 cusr 0.01 csys = 0.17 CPU)
Result: PASS
prove
コマンドの-v
は結果の表示を詳細に、-c
は色を付けて、--exec
はテストコードの実行コマンド、--ext
はテストコードの拡張子ですね。
ちょっとハマったのが、配列同士をassert.equal()
で比較するとfalseになってしまうこと。確かにnode
コマンドのコンソールで[1,2] == [1,2]
と打ってもfalseが返ってきます。このへん、JavaScriptの動作としては配列の中身までは比較せずに、同じオブジェクトへの参照を持っているかどうか、ということを比較しているからfalseなのでしょうか。assert.deepEqual
を使うことで意図通りのテストができました。
せっかくなのでもう一つテストファイルを足してみます。
t/string_test.js
require('../test_helper.js');
QUnit.module('Module String');
QUnit.test('#match', function() {
var str = 'test';
assert.ok(str.match(/te.{2}/));
});
QUnit.test('#split', function() {
var str = 't,e,s,t';
assert.deepEqual(str.split(','), ['t', 'e', 's', 't']);
});
QUnit.start();
Greenでした。OKですね。
では、独自に作ったJSをテストすることにします。
テストファーストということで、テストファイルから。
t/calculator_test.js
require('../test_helper.js');
var cal = require('../lib/calculator.js').Calculator;
QUnit.module('Module Calculator');
QUnit.test('#initialize', function() {
assert.ok(cal);
});
QUnit.start();
この時点でテストを実行すると当然Red。
実装しましょう。
lib/calculator.js
var Calculator = {
};
exports.Calculator = Calculator;
JSのモジュールとか、やったことは無いのですがこんなやり方でいいのかな?とにかくGreenになったので、テストコードを足して失敗になったことを確認した後、実装します。
t/calculator_test.js
QUnit.test('#sum', function() {
assert.equal(10, cal.sum(5, 5));
assert.equal(15, cal.sum(5, 5, 5));
});
lib/calculator.js
var Calculator = {
sum: function() {
var result = 0;
for (var i = 0, l = arguments.length; i < l; i++) {
result += arguments[i];
}
return result;
}
};
GreenになったのでOKのようですね。
自動でテストを実行
やっぱりテストファイルを更新したら自動的に更新されて欲しいですよね。
とりあえずrubyですがguardを使ってみました。
$ gem install guard guard-shell growl rb-fsevent
$ guard init shell
生成されたGuardfileを以下のように書き換えて、lib/, t/以下のファイルが更新されたら自動的にprove
コマンドを実行するようにしました。
guard 'shell' do
watch(%r{lib|t/(.+)\.js}) {|m| `prove -vc --ext=.js --exec=node` }
end
結果、JSとperlとrubyを駆使した(?)面白い環境になりました。
色がつかないのでなんとかしたいのですが、どうすればいいのかよくわからなかったです。
まとめ
今回はnode.jsを使って、簡単なモジュールのテストをしてみました。
一口にJSのテストと言っても、サーバーサイドのJSをテストしたいのか、ブラウザ上のJS(特にDOM操作が入ったりするもの)をテストしたいのか、JS含めたアプリケーションの動き全体をテストしたいのか、そのへんを見極めておかないといったい自分が何をしたいのかを見失ってしまいそうで、要注意ですね。