1. Automated Testing
FirebugなどJavaScriptデバッガーはいろいろあって便利だけど、Humans are lazyだ。
デバッガーによるデバッグは手動で行っているので、そこにAutomated testingをもうけて自動化しよう。
JavaScriptの自動テストのソフトウェアの歴史は2001年のJsUnitまでさかのぼる。
2004年にSeleniumがでてきて、 JavaScript/web developmentは今も活気があるコミュニティだ。
デバッガーによるデバッグは手動で行っているので、そこにAutomated testingをもうけて自動化しよう。
JavaScriptの自動テストのソフトウェアの歴史は2001年のJsUnitまでさかのぼる。
2004年にSeleniumがでてきて、 JavaScript/web developmentは今も活気があるコミュニティだ。
■1.1 The Unit Test
Unitテストはコンポーネント単体で実行すべきであり、他のテストに依存するべきではない。場合によっては単体で実行できるようにするためにmockやstubといったものを必要にするときがあるだろう。(この辺はPart III, Real-World Test-Driven Development in JavaScript で)
いつUnitテストを実行するのかを書き出してみると
- 実装が完了したとき、正しい挙動しているかを確認
- 実装を変更したとき、挙動が変わってないかを確認
- 新しいUnitテストを追加したとき、それが本来の目的を満たしているかを確認
1.1.1 Unit Testing Frameworks
まだUnitテストをしたことないと思っていても実際には似たような事をやっている場合もあります。(コンソールでインスペクトして確認するなど)ただしこれは自動化されていないし、また再利用ができていません。
xUnit test caseという定式化されたものを学んでいく。
xUnitはJUnitをポートした、もしくはそれのアイデアをベースとしたテストフレームワークの事を言います。
もっと正確にいうとSUnitというSmalltalkのテストフレームワークのアイデアとコンセプトがベースにあります。@ Kent Beck
1.1.2 strftime for JavaScript Dates
多くのプログラミング言語はタイムスタンプを出力する strftime 関数に似たものを持っているので、これを例にする。JavaScriptには無いので作る…
Date.prototype.strftime = (function() {
function strftime(format) {
var date = this;
return (format + "").replace(/%([a-zA-Z])/g, function(m, f) {
var formatter = Date.formats && Date.formats[f];
if (typeof formatter == "function") {
return formatter.call(Date.formats, date);
} else if (typeof formatter == "string") {
return date.strftime(formatter);
}
return f;
});
}
// Internal helper
function zeroPad(num) {
return (+num < 10 ? "0" : "") + num;
}
Date.formats = {
// Formatting methods
d: function(date) {
return zeroPad(date.getDate());
},
m: function(date) {
return zeroPad(date.getMonth() + 1);
},
y: function (date) {
return zeroPad(date.getYear() % 100);
}
Y: function(date) {
return date.getFullYear();
},
// Format shorthands
F: "%Y-%m-%d",
D: "%m/%d/%y"
};
return strftime;
}());
このDate.prototype.strftimeは大きく分けるとreplace関数とDate.formatsからできていて、次のように分解できる
- Date.formats はキーと日付から対応するデータを値として抽出するメソッドようなフォーマット指定子を持つオブジェクト。
- いくつかのフォーマット指定子は便利なショートカットを持ってる
- String.prototype.replace はフォーマット指定子にマッチする正規表現%([a-zA-Z])と一緒に使う
- 置換関数は指定子がDate上で利用可能かをチェックする。
一個づつコンソールに入れてやっていくのは大変だ...
>>> var date = new Date(2009, 11, 5);
>>> date.strftime("%Y");
"2009"
>>> date.strftime("%m");
"12"
>>> date.strftime("%d");
"05"
>>> date.strftime("%y");
"9"
毎回これをやるのは大変だから、小さなHTMLページを作ってそこに書いていこう。jsファイルを読み込むHTMLを作ってjsにはコンソールに出力するように書いておけば、毎回入力しなくてもみることができるだろう
var date = new Date(2009, 11, 5);
console.log(date.strftime("%Y"));
console.log(date.strftime("%m"));
console.log(date.strftime("%d"));
console.log(date.strftime("%y"));
console.log(date.strftime("%F"));
■1.2 Assertions
Unitテストの中心はassertionだ。1.1ではコンソールに出力して、目視によってそれが正しいかを確認していたが、Unitテスト内ではassertionを使って自動的にそれらをチェックするようにする。
function assert(message, expr) {
if (!expr) {
throw new Error(message);
}
assert.count++;
return true;
}
assert.count = 0;
このassert関数は第二引数がtruthy(false, null, undefined, 0, "", NaNではない)かをチェックするシンプルなもの。これを先ほどのコンソール出力の代わりにする。
var date = new Date(2009, 9, 2);
try {
assert("%Y should return full year", date.strftime("%Y") === "2009");
assert("%m should return month", date.strftime("%m") === "10");
assert("%d should return date", date.strftime("%d") === "02");
assert("%y should return year as two digits", date.strftime("%y") === "09");
assert("%F should act as %Y-%m-%d", date.strftime("%F") === "2009-10-02");
console.log(assert.count + " tests OK");
} catch (e) {
console.log("Test failed: " + e.message);
}
先ほどより若干量が増えたが、テスト自体がどこがおかしいを自動的に知らせてくれるようになった。1.2.1 Red and Green
Unitテストの世界では"red","green"という言葉が"失敗","成功"の代わりに使われている。下のように出力する際にDOMに色を付けて視覚的にするのもいいかも
function output(text, color) {
var p = document.createElement("p");
p.innerHTML = text;
p.style.color = color;
document.body.appendChild(p);
}
// console.log can now be replaced with
output(assert.count + " tests OK", "#0c0");
// and, for failures:
output("Test failed: " + e.message, "#c00");
■1.3 Test Functions, Cases and Suites
さきほどのassert関数ではテストが失敗したときにエラーをthrowするだけでした。なので、よりよりフィードバックをするようにtestCaseという関数に一連の動作をまとめてみる。
function testCase(name, tests) {
assert.count = 0;
var successful = 0;
var testCount = 0;
var hasSetup = typeof tests.setUp == "function";
var hasTeardown = typeof tests.tearDown == "function";
for (var test in tests) {
if (!/^test/.test(test)) {
continue;
}
testCount++;
try {
if (hasSetup) {
tests.setUp();
}
tests[test]();
output(test, "#0c0");
if (hasTeardown) {
tests.tearDown();
}
// If the tearDown method throws an error, it is
// considered a test failure, so we don't count
// success until all methods have run successfully
successful++;
} catch (e) {
output(test + " failed: " + e.message, "#c00");
}
}
var color = successful == testCount ? "#0c0" : "#c00";
output("<strong>" + testCount + " tests, " +
(testCount - successful) + " failures</strong>",
color);
}
このtestCaseを使って、strtimeをテストする。
/*globals assert, testCase*/
testCase("strftime test", {
setUp: function () {
this.date = new Date(2009, 9, 2, 22, 14, 45);
},
"test format specifier %Y": function () {
assert("%Y should return full year",
Date.formats.Y(this.date) === 2009);
},
"test format specifier %m": function () {
assert("%m should return month",
Date.formats.m(this.date) === "10");
},
"test format specifier %d": function () {
assert("%d should return date",
Date.formats.d(this.date) === "02");
},
"test format specifier %y": function () {
assert("%y should return year as two digits",
Date.formats.y(this.date) === "09");
},
"test format shorthand %F": function () {
assert("%F should be shortcut for %Y-%m-%d",
Date.formats.F === "%Y-%m-%d");
}
});
setupで共通の処理を書けば、重複したコードをテストに入れないで良くなる。1.3.1 Setup and Teardown
xUnitフレームワークは大抵setUpとtearDownというメソッドを提供します。先ほどの例のようにtestCaseを渡して、テストを実行する前にsetUpメソッドが実行されるので、共通の処理に必要なものを先に定義できます。
tesrDownは逆にテストが終わった後に実行し、後片付けなどに使われます。
testCase("strftime test", {
setUp: function () {
this.date = new Date(2009, 9, 2, 22, 14, 45);
},
"test format specifier %Y": function () {
assert("%Y should return full year",
Date.formats.Y(this.date) === 2009);
}
...
})
■1.4 Integration Tests
Unitテストがwheel, wheels, electric windows などの部品だとすると、Integrationはそれらをくみ上げた車。シンプルな Integration TestsはUnitテストに似ている。
■1.5 Benefits of Unit Tests
"Writing tests is an investment."テストを書くのは時間がかかるという反論もあるけど、自動化したテストにより繰り返しテストが行える。
1.5.1 Regression Testing
回帰テスト人は時に間違いを犯すため製品にバグが混入することがある。
最悪な時はそれを直した後にまた再発することがある。
それを避けるための手助けをするのがRegression Testingだ。
自動化されたテストは過去に戻りテストを行うこともできる。
1.5.2 Refactoring
リファクタリングは動作を維持したまま実装を変更する必要があります。リファクタリングには失敗しやすいポイントがたくさんあります。変数名を変えたときに参照してるのを変え忘れる、スコープの領域など…
"変更するコードセクションの固定テストセットを作成せよ" via Refactoring: Improving the Design of Existing Code
1.5.3 Cross-Browser Testing
大変な労力を払う部分だがシンプルなUnitテストを作っておけば後はクレバーな test runnerがあればいけるはず。
■1.6 Pitfalls of Unit Testing
Unitテストを書くのは必ずしも簡単ではない。いいテストを書くためには練習が必要です。悪いテストを書いたらテストの利点が得られない。
真によいUnitテストを書くためには、テストしやすいコードが必要になるでしょう。
この書籍ではテストしやすいコードと、よいUnitテストについて示していくつもりだ。
■おわり
結構読みにくい(英語力が足りない的な意味で)本かもしれない。普通のJavaScriptのコード部分がほとんどテストコードなので、そう感じるのかもしれないな。
ただ、分かりにくいという意味ではない。
テスト例は順を追っていけるようにgitでバージョン管理されたものも配布されている。
コメント(0件)
- TB-URL http://efcl.info/adiary/0115/tb/