たまに18歳未満の人や心臓の弱い人にはお勧めできない情報が含まれることもあるかもしれない、甘くなくて酸っぱくてしょっぱいチラシの裏。RSSによる簡単な更新情報を利用したりすると、ハッピーになるかも知れませんしそうでないかも知れません。
の動向はもえじら組ブログで。
宣伝。日経LinuxにてLinuxの基礎?を紹介する漫画「シス管系女子」を連載させていただいています。 以下の特設サイトにて、単行本まんがでわかるLinux シス管系女子の試し読みが可能!
Page 1/1: 1
中野さんが、JavaScriptにはsleep(一定時間待ってから次の処理に進むという命令文)が無いせいでテストを書くのに難儀したという話を書かれているけれども。まさにこれをどうにかしたくて、UxUは進化してきたようなものと言える。
知ってる人は知ってるだろうけど、Firefox/Thunderbirdアドオン向けの自動テスト実行ツール・UxUでは、テストを書く上でsleepに相当する機能を利用できる。これは、JavaScript 1.7でジェネレータ・イテレータ機能を実現するために追加されたyield式のトリッキーな使い方だ。
これを実現しているのは、lib/utils.jsのdoIteration()
と、test/test_case.jsのrun()
ということになる。ここから要点だけを取り出すと、以下のような事をしている。
まず、スクリプトの書き手は、「sleepを使いたい処理」を関数として定義する。この時、sleep
の代わりにyield
を使う。
var task = function() {
doSomething1();
yield 1000;
doSomething2();
yield 1000;
doSomething3();
}
次に、スクリプトの書き手は、この関数を後述するdoIteration()
に渡す。すると、doIteration()
がよしなに計らって、「doSomething1()
を実行した後、1000ミリ秒待って、doSomething2()
を実行し、また1000ミリ秒待って、doSomething3()
を実行する」という風な形で先程の関数の内容を実行する。
この時のdoIteration()
の内容は、以下のような感じ。
function doIteration(aTask) {
if (typeof aTask == 'function') {
// 渡されたのが関数だったら、まず、評価した返り値を得る。
aTask = aTask();
}
if (!aTask ||
!('next' in aObject) ||
!('send' in aObject) ||
!('throw' in aObject) ||
!('close' in aObject) ||
aObject != '[object Generator]') {
// 渡されたオブジェクトまたは関数の返り値が
// ジェネレータ・イテレータではない場合、何もしない。
return;
}
// ここからがミソ!
// 全部の処理が終わったかどうか、を示すオブジェクトを定義
var finishFlag = { value : false, error : null };
var last = 0; // スリープ開始時点の時刻を保持する変数
var sleep = 0; // スリープの長さ(秒数)を保持する変数
var timer = window.setInterval(function() {
if (
// スリープの長さがちゃんと指定されていて
sleep > 0 &&
// スリープ開始時点からの経過時間がスリープとして
// 指定された時間未満であれば
(Date.now() - last) < sleep
) {
// ここで処理を終えて、100ミリ秒後まで待つ。
return;
}
// スリープとして指定された時間が経過したので、処理を進める。
try {
// 次にyieldが登場するまでの間の処理を実行。
sleep = aTask.next();
// next()の返り値はyield式に渡された値。
last = Date.now(); // スリープ開始時刻を保持して
return; // 処理を一旦終えて100ミリ秒後を待つ。
}
catch(e if e instanceof StopIteration) {
// 最後のyield式の後の内容が実行されて、定義された関数の内容が
// 最後まですべて実行されると、StopIteration例外が発生する。
// よって、処理完了とみなす。
finishFlag.value = true;
}
catch(e) {
// それ以外の未知の例外が発生した時は、処理中断とする。
finishFlag.error = e;
}
// 100ミリ秒ごとの繰り返し処理を停止。
window.clearInterval(timer);
}, 100);
return finishFlag;
}
UxUの内部でやってる事は基本的にはこういう事。ただ、実際にはもうちょっと使い勝手をよくするために細かい処理が加わってる。
doIteration()
が中で何をやってるのかを知らなければ、パッと見は、sleepという命令文の名前がyieldに変わっただけのようにすら見えるんじゃないだろうか。そこが、このやり方の狙いだ。スクリプトの書き手はタイムアウトだのコールバックだのといった難しい事を何も考えなくても良くて、単に「sleep文に相当する機能が加わったJavaScript」として好きなように処理を書く事ができる。テストを書くための工数が大幅に削減される(かもしれない)ので、テストを書くのが苦にならず、ばりばりテストを書けるようになる(はず)。その結果、充実したテストのおかげでより安心して開発に専念できるようになる(はず)。という理屈です。
ちなみに、勘のいい人は気付くだろうけど、setInterval()
に渡している関数の冒頭に以下の内容を挿入すれば、doIteration()
をいくらでも入れ子にできるようになる。
if (
typeof sleep == 'object' &&
(!sleep.value || !sleep.error)
) {
return;
}
var task = function() {
doSomething1();
yield 1000;
yield doIteration(function() {
doSomething2();
yield 500;
doSomething3();
yield doIteration(function() {
doSomething4();
yield 100;
doSomething5();
});
});
doSomething6();
};
doIteration(task);
さらに、こんなこともできる。
var task = function() {
doSomething1();
yield doIteration(function() {
var flag = { value : false; }
frame.addEventListener('load', function() {
frame.removeEventListener('load', arguments.callee, false);
flag.value = true;
}, true);
frame.contentDocument
.defaultView
.location.href = 'http://www.example.com/';
yield flag;
// フレームの読み込みが終わったらここに進む
doSomething2();
});
doSomething3();
};
doIteration(task);
この辺をもっと簡単に書けるようにヘルパーメソッドを色々と整備したのがUxUのテスト実行環境、と思ってもらえれば大体それで正解です。
25日追記。他にも色々やり方があるようです。(どっちもMozilla限定だけど)
Browser chrome tests - MDC 見ながらなんとか環境整えてビルドしてテスト実行して……という風な事をしてみたけど、テスト結果のあまりのわかりにくさに閉口した。しかも一個テストがこけたら後の物も続けて失敗するし。クリーンなプロファイルで起動してくれるのはいいけど、あとがもうメタメタすぎて、とても常用(?)する気にならないよ……
というわけでUxUの次のテーマはFirefoxのソースツリー上にある自動テストを実行する機能ということにしたいと思います。
waitForExplicitFinish()
とfinish()
での非同期テストをサポートする。この辺が鍵でしょうか。
追記。概要をつかむために説明を翻訳した。これによると、「browser_*.js」という名前のファイルだけがブラウザ用のテストとして認識されるようなので、ファイル名がこのルールに一致していたら上記のような特別な処理を行うようにする、という感じで行けそう。
UxU(UnitTest.XUL)を利用したFirefoxアドオンのデバッグの例 - ククログ(2008-11-17)
XUL/Migemo 0.11.7での修正内容が典型的な「自動テストを使ったデバッグ」だったので、UxUのチュートリアルを兼ねて、会社のサイトの方に書いてみました。UxUの解説って言うよりは、テスト駆動開発自体の解説という気もしますが。
リンク先に解説してるのはpXMigemoFindのfindFirstVisibleNodeメソッドだけのデバッグ話ですが、実際にはこのメソッドはだいぶ根幹に関わる物で、このメソッドの挙動の変更によって他の機能に色々と影響が出る可能性がありました。が、他の挙動に関しては一通り自動テストを作成済みだったために、後退バグの発生で収拾不能な事態に陥るということを恐れずに安心して修正に取り組むことができた、というまさに自動テスト様々な事例だったということも忘れずに付け加えておきたい所です。
会社サイトのブログの方で解説を書くつもりですが、UxU新バージョン(0.5.0)を公開しました。日本時間10月30日の昼下がり、太平洋標準時で29日の肉の日リリースです。変更点はMozilla Add-onsのバージョン詳細の方を見てください。
自分自身がサーバソケットの方を使ってなかったのでMozRepl互換のサーバ機能の方がだいぶ長らく死亡してたみたいなんですが、今回プロファイルを指定してテストを実行する機能を作るにあたり、新たに起動された子プロセスと親プロセス側でテストの実行結果や中止の命令とかを通信しあうために、UxUサーバで使われていた機能をブラッシュアップして流用するようにしたため、ぶっ壊れていた所がだいぶ直りました。今後はサーバ周りの実装も気をつけて見るようになると思いますので、ぶっ壊れっぱなしのまま放置ということは減るんじゃないかと思います。多分。
ところで、子プロセス起動してうんたらかんたらということができるようになったということは、FirefoxやThunderbirdの上で動かすことにこだわらなくてもいいようになった(必要に応じてそれらを呼び出せば済む)という事で、やる気次第ではXULRunnerアプリケーション化という道もあり得るかもしれませんね。
UnitTest.XULとかUxUとかうずとかいうアレ。サイトの方は長らく放置してますが、地味に作業してます。
プロファイルを指定してテストを実行する、というのはずっと前からやりたいと思ってた。これができると機能テストとか結合テストとかいう段階のテストが圧倒的に楽になる……はず。
UxUの自己テストを(現実逃避に)ようやく書き始めてみている。今まで無かったのかよって怒られそうだけど……
アサーションのモジュールのテストとかテストケースのテストとか、書いてて頭がこんがらがってくる。
会社のサイトのコンテンツが一新され、コンテンツにブログが加わりました。何をやってる会社なのか(何ができるのか)分からんと言われがちなようなので、みんながちまちまやってる内容をここに書いていって、「当社ではこういうことができます」的な宣伝材料にする、というのが一応の目的のようです。
で、自分もアドオン開発者向けの自動テストツールであるUxUの使い方の解説なんかを書いてみました。ツールの使い方というよりは、「はじめての自動テスト」みたいな感じになってしまってますが。自分自身がまだ自動テストのことをよく分かってなくて須藤さんとかにしょっちゅう「何考えてんだ(全然分かってねえな)……」的な指摘を受けてばかりなので、自分の理解レベルに合わせるとこんな感じになってしまいました。技術力とか実績とかのアピールの場のはずなのに、こんな低レベルな記事を載せてしまうと却って会社の印象に泥を塗ってしまうんじゃなかろうか、ということをamachangのエントリとか見て思いもしましたが、もう後の祭りです。
UxUのマニュアルのつもりで、またそのうちXUL/Migemoのテストでの利用例の解説の続きを書くかもしれません。
あと、サービス→Mozillaサポート 実績紹介→Thunderbird用アドオンと辿ったページでThunderbird用の地味なパッチもいくつか公開しています。中にはもしかしたら同種のアドオンがすでに世の中にある車輪の再発明な物もあるのかもしれませんが、探すより作った方が早かったんで……
regression(後退バグ。修正のために加えた変更が原因で新たな問題が発生すること)のせいで体力・精神力を消耗する事が重なり、自動テストドリブンな開発の重要さを身に染みて感じた。プレゼンでも言ったけど、「やってりゃ良かった」と後悔してばかりだ。まさかこんなに手こずる羽目になるとは当初は思っていなかったから。
自動テストはregressionの発生を防ぐ(正確には、regressionを残したままでいることを防ぐ)素晴らしいメソッドだ。と今になって改めて思う。
でも、どんなコードでも機械的に自動テストにかけられるわけではない。機械的に自動テストを実施するには、自動テストを実行しやすい設計になっていなければならない。自動テストを実行しやすい設計とは、粒度が小さい=オブジェクトやらメソッドやらが可能な限り細かい単位で分割されている設計のこと。テスト対象となる部品自体が可能な限り自己完結していて、外部的な要素は必要に応じてすべてパラメータとして与えるようになっていること。横着して一つの関数内からグローバル変数やら何やらを参照しまくっていると、その関数のロジックそのものをテストすることができない。
そういうわけで、自動テストドリブンな開発には気を遣わなければならない。気を遣わないといけないから気力を消耗する。だから、せずに済むのなら、しないままでいたい。でも、そう考えているうちに「自動テストを作成する&テストを実施しやすい設計にするコスト」と「やっつけで作って、その都度メンテナンスするコスト」の関係が、「前者>後者」だったのがいつの間にか「前者<後者」に逆転してしまうようになっていて、メチャメチャ後悔することになる。
今まで自分のやってきたことは9割方、自動テスト無しでもどうにかやってこれていたし、そもそも、自動テストの重要性とそのための設計の指針が分かった今改めて見返してみても、自動テスト化することが困難な物が多かった、と思う。だから、自動テストを前提にしてコードを書くという習慣が身についていない。自動テストが最初から不可能な事が多かったから、自動テストのできない設計にするしかなかったから、自動テストのできない設計にすることが当たり前になってしまっている。
でもいい加減、その悪習を断ち切らなければならない。25歳ももうすぐ終わりの、四捨五入すれば三十路の、今更も今更で手遅れ感がとても強いけれども、やらなければいけない。今まで自分がやってきた方法は通用しないということを自覚しないといけない。できて当たり前の事が今まで全くできていなかったという事、本当はこの面子の中で飛び抜けて一番遅れているという事、それなのに対等であるように勘違いして思い上がっているという事、今まで他人事だと思ってた「ダメな人の典型」に自分がまったく当てはまっているという事、全部認めないといけない。
そういうわけでとりあえずUxUはマジオススメ。
Page 1/1: 1