2014年1月17日、
エンジニアサポートCROSS 2014とは
「複数の技術を身につけなければWebサービスは作れない=クロスしないと生きていけない」
「実況解説つき!ペアプロでわかるJavaScriptテスト入門」
このセッションは
それでは、

登壇者
本セッションの登壇者は、




はじめに
本セッションで使われたソース一式は吾郷さんがGitHub上にアップロードしています。
ダウンロードしたファイルを確認してみると、testem.
{
"src_files": [
"js/lib/*.js",
"test/lib/*.js",
"setup.js",
"js/*.js",
"test/*.js"
],
"test_page": "tests.mustache"
}}
setup.mocha.
はMochaというフレームワークも利用するので、
function updateLabel() {
return true;
}
mocha.setup('bdd');
また、/js/ディレクトリ内にテストしたいjsファイルが、/test/ディレクトリ内にテストするためのjsファイルが含められています。
準備
まずはコマンドラインからtestemのインストールを行います。
npm -g install testem
インストールができたらtestem
コマンドを打ってtestemを起動させましょう。testemはJavaScriptを実際にウェブブラウザ上で実行してOK/

これは既に用意されていた/test/
function firstTest() {
return true;
}
describe('0', function() {
it('trueが返ってくるか確認する', function() {
expect(firstTest()).to.eql(true);
});
});
テストツールについて
ここで、

佐藤さん曰く
今回使っているのはこの図でいう3つ。具体的には次のものです
- リモートテストランナーとしてのtestem
- テスティングフレームワークとしてのMocha
- describe/
itというようなテスト形式で書いてそれを実行するというところをやってくれる
- describe/
- アサーションライブラリとしてのexpect.
js expect(window.
のような形で、r).to. be(undefined); window. rはundefinedですよというのをアサーションする (確認する)
また、
なぜtestemが必要かということについては、
例題1
それでは本題。よくある、
$(function() {
$('.jQuery.button').click(function() {
updateLabel();
});
});
これに対するテストコードはこちら。
// 筆者注:describeでこのテストがどういうテストかを宣言
describe('1', function() {
// 筆者注:itでこのテストがどういうルールによって展開して何を確認したいかを宣言
it('ボタンをクリックしてupdateLabelを表示する', function() {
// 筆者注:updateLabel()が実行されたかは外からはわからないので、
// それがわかるように別のfunctionに置き換える。
// Sinon.JSというライブラリを利用
sinon.stub(window, 'updateLabel');
// 筆者注:testInitというfunctionを実行、jQueryの$関数を一時的に置き換える(後述)
var init = window.testInit.args.shift();
init[0]();
$('.jQuery.button').click();
expect(updateLabel.called).to.eql(true);
window.updateLabel.restore();
});
});
ここで、
setup.
window.testInit = sinon.stub();
また、/js/
(window.testInit || $)(function() {
これによって、$
を使って初期化をするようになります。本来であれば読み込まれてすぐに$
関数で初期化されてしまうのを別のもので初期化するようにしています。
この状態でブラウザを見てみると、

ここで使ったSinon.
和田さんからもモックライブラリについての詳しい解説があり、
例題2
次はこれまたよくあるタイマーを用いたコードを見ていきます。
(window.testInit || $)(function() {
setTimeout(function() {
updateLabel();
}, 10);
});
10msだけなら待つことは簡単ですが、
describe('2', function() {
it('非同期でupdateLabelを表示する', function() {
sinon.stub(window, 'updateLabel');
// 筆者注:時間を一時的に仮のものに置き換える
var fakeclock = sinon.useFakeTimers();
var init = window.testInit.args.shift();
init[0]();
// 筆者注:仮に置き換えた時間を100ms進める
fakeclock.tick(100);
expect(updateLabel.called).to.eql(true);
window.updateLabel.restore();
fakeclock.restore();
});
});
このように、sinon.
によりJavaScript世界での時間が止まり(時間が勝手には進まなくなる、setTimeout()
、setInterval()
、date()
などが止まる)、.tick()
で時間を自由に進められるようになるとの解説が佐藤さんからありました。
また、
describe('User', function() {
describe('#save()', function() {
// 筆者注:引数にdoneというものをとって、
it('should save without error', function(done) {
var user = new User('Luna');
// 筆者注:例えばsaveという非同期な関数(setTimeoutなど)
user.save(function(err) {
if (err) throw err;
// ここでdone()が実行されるまではこのテストケースが終了しない
done();
});
})
})
})
非同期のテストをする際には基本的にはsinon.
をオススメしますが、done
がかなり使いやすいということです。
例題3
その次に、
(window.testInit || $)(function() {
$.get('/').done(function(result) {
updateLabel(result);
});
});
これのテストをそのままやろうとすると、
describe('3', function() {
it('APIレスポンスをupdateLabelする', function() {
// 筆者注:サーバにアクセスしている部分をダミーに置き換える
var fakeserver = sinon.fakeServer.create();
// 筆者注:置き換えたサーバがどういう振る舞いをするかを設定
fakeserver.respondWith('/', [200, {
'Content-Type': 'text/plain'
}, 'result']);
sinon.stub(window, 'updateLabel');
var init = window.testInit.args.shift();
init[0]();
// 筆者注:サーバからレスポンスを返してくる、という動作を記述
fakeserver.respond();
expect(updateLabel.called).to.eql(true);
window.updateLabel.restore();
fakeserver.restore();
});
});
コード中、sinon.
によってhttpリクエストが内部的に書き換えられて、respondWith
の内容を設定する方法のほかに、respond
の中で書くこともできるそうです。
例題4
最後に、
(window.testInit || $)(function() {
$('.all.button').click(function() {
$(this).find('.form').slideToggle(300, function() {
updateLabel();
});
});
$('form').on('submit', function(e) {
e.preventDefault();
$.post('/', $(this).serialize()).done(function(result) {
updateLabel(result);
});
});
});
この時点で残念ながら時間切れとなってしまい、
テストケースを日本語を書くことについて
ここで、it
の中で日本語でテストケースを書いていますね)。
これに対する和田さんの回答は
まとめ
今回のセッションではJavaScriptでほとんどテストを書いたことが無い人が今日からでもユニットテストが始められるように、
実は、
記事の中で見てきたようにテストのための使いやすそうなツールがこれだけある中、
(私もきちんとテストがんばろう…と思います。)