クリアコードはフリーソフトウェア開発で培った技術力を提供しています。特にMozilla製品(Mozilla FirefoxとMozilla Thunderbird)とRubyに関連した開発を得意としています。
2010年1月29日付で、テスティングフレームワークUxUのバージョン0.7.6をリリースしました。
utils.wait()について今回のアップデートでの目玉となる新機能は、非同期な機能のテストをより簡単に記述できるようにするヘルパーメソッドであるutils.wait()です。これは、以下のように利用します。
function testSendRequest() { myFeature.sendRequest(); utils.wait(1000); // 1000ミリ秒=1秒待つ assert.equals('OK', myFeature.response); } function testLoad() { var loaded = { value : false }; content.addEventListener('load', function() { content.removeEventListener('load', arguments.callee, false); loaded.valeu = true; }, false); myFeature.load(); utils.wait(loaded); // valueがtrueになるまで待つ assert.equals('OK', content.document.body.textContent); }
また、utils.wait()は関数の中でも利用できます。
function testSendRequest() { function assertSend(aExpected, aURI) { myFeature.sendRequest(aURI); utils.wait(1000); assert.equals(aExpected, myFeature.response); } assertSend('OK', '...'); assertSend('NG', '...'); assertSend('?', '...'); }
utils.wait()が受け取れる値は、これまでの処理待ち機能で yieldに渡されていた値と同じです。詳しくは処理待ち機能の利用方法をご覧下さい。大抵の場合、yield Do(...);と書かれていた箇所は、utils.wait(...);へ書き換えることができます。
ただし、このヘルパーメソッドはFirefox 3以降やThunderbird 3以降など、Gecko 1.9系の環境でしか利用できません。Thunderbird 2などのGecko 1.8系の環境ではエラーとなりますのでご注意下さい。(それらの環境でもテストの中で処理待ちを行いたい場合は、従来通りyieldを使用して下さい。)
これまでUxUでは、「機能を実行した後、N秒間待ってから、機能が期待通りに働いたかどうかを検証する」「初期化処理で、ページの読み込みの完了を待ってから次に進む」といった処理待ちを実現する際は、JavaScript 1.7以降で導入されたyieldを使う仕様となっていました。
yieldを含む関数はジェネレータとなり、関数の戻り値をイテレータとして利用できるようになります。この時ジェネレータの内側から見ると、「yieldが出現する度に処理が一時停止する」という風に考えることができます。
function gen() { alert('step1'); yield 'step1 done'; alert('step2'); yield 'step2 done'; alert('step3'); } var iterator = gen(); // この時点ではまだ関数の内容は評価されない var state; state = iterator.next(); // 'step1' が表示される alert(state); // 'step1 done' が表示される state = iterator.next(); // 'step2' が表示される alert(state); // 'step2 done' が表示される try { state = iterator.next(); // alert('step3'); が実行される } catch(e if e instanceof StopIteration) { // 次のyieldが見つからないので、StopIteration例外が投げられる }
UxUに従来からある処理待ち機能は、この考え方を推し進めて作られています。テスト関数の中にyieldがある場合(つまり、関数の戻り値がイテレータとなる場合)は、フレームワーク側で自動的にイテレーションを行い、yieldに渡された値をその都度受け取って、次にイテレーションを行うまでの待ち条件として利用しています。例えば、数値が渡された場合はその値の分の時間だけ待った後で次にイテレーションを行う、といった具合です。
このやり方の欠点は、yieldを含む関数から任意の戻り値を返すことができないという点です。
function gen() { alert('step1'); yield 'step1 done'; alert('step2'); return 'complete'; } try { var iterator = gen(); } catch(e) { alert(e); // TypeError: generator function gen returns a value }
returnを書くと、関数の実行時にエラーになってしまいます。どうしても何らかの値を取り出したい場合は、値を取り出すためのスロットとなるオブジェクトを引数として渡すなどの工夫が必要になります。
function gen(aResult) { alert('step1'); yield 'step1 done'; alert('step2'); aResult.value = 'complete'; } var result = {}; var iterator = gen(result); var state; state = iterator.next(); // 'step1' が表示される alert(state); // 'step1 done' が表示される try { state = iterator.next(); // 'step2' が表示される } catch(e if e instanceof StopIteration) { alert(result.value); // 'complete' が表示される }
また、ジェネレータは実行してもその段階では関数の内容が評価されないという点にも注意が必要です。例えば以下のようなテストは、期待通りには動いてくれません。
function testSendRequest() { function assertSend(aExpected, aURI) { myFeature.sendRequest(aURI); yield 1000; assert.equals(aExpected, myFeature.response); } assertSend('OK', '...'); assertSend('NG', '...'); assertSend('?', '...'); }
この例では、assertSend()を実行したことで戻り値としてイテレータが返されているものの、そのイテレータに対するイテレーションが一切行われていないため、リクエストも行われなければアサーションも行われないということになってしまっています。これは以下のように、返されたイテレータをそのままフレームワークに引き渡して、フレームワーク側でイテレーションを行わせる必要があります。
function testSendRequest() { function assertSend(aExpected, aURI) { myFeature.sendRequest(aURI); yield 1000; assert.equals(aExpected, myFeature.response); } yield assertSend('OK', '...'); yield assertSend('NG', '...'); yield assertSend('?', '...'); }
また、このままではジェネレータの中で発生した例外のスタックトレースを辿れないという問題もあります。スタックを繋げるためには、ヘルパーメソッドのDo()を使ってyield Do( assertSend('OK', '...') )のように書かなければなりません。
utils.wait()を使う場合、これらのことを考慮する必要はありません。冒頭のサンプルコードのように、素直に書けば素直に動作してくれます。
utils.wait()がどのように実装されているかについても解説しておきます。
このメソッドの内部では、Gecko 1.9から実装されたスレッド関連の機能を利用しています。
window.setTimeout(function() { alert('before'); }, 0); alert('after');
JavaScriptは基本的にシングルスレッドで動作するため、このようにタイマーを設定すると、その処理はキューに溜められた状態となります。その上で、メインの処理が最後まで終わった後でやっとキューの内容が処理され始めるため、この例であれば「after」「before」の順でメッセージが表示されることになります。
var finished = false; window.setTimeout(function() { alert('before'); finished = true; }, 0); var thread = Cc['@mozilla.org/thread-manager;1'] .getService() .mainThread; while (!finished) { thread.processNextEvent(true); } alert('after');
Gecko 1.9以降のスレッド機能を使うと、メインの処理を一旦中断して先にキューに溜められた処理の方を実行し、その後改めてメインの処理に戻るということができます。実際に、こちらの例では「before」「after」の順でメッセージが表示されます。UxU 0.7.6ではこれを応用して、任意の条件が満たされるまでthread.processNextEvent(true);でメインの処理を停止し続けることによって、処理待ちを実現しています。
なお、HTML5にもWeb Workersというスレッド関係の機能がありますが、こちらは別スレッドでスクリプトを動作させる機能しか持っていないため、上記のようなことは残念ながらできません。
UxU 0.7.6からは、utils.wait()を使ってより簡単に処理待ちを行えるようになりました。Firefox 3以降やThunderbird 3以降専用にアドオンを開発する際には、是非利用してみて下さい。
こんにちは。下田(Piro)です。
昨日2009年11月8日に開催されたFirefox Developers Conference 2009にて、トークセッション「Aza Raskin に一問一答!」にパネリストとして参加しました。また、懇親会でのライトニングトーク第2部にて発表を行いました。参加された皆様、お疲れ様でした。また、セッションにお越しいただいた皆様、誠にありがとうございます。
発表資料および映像は以下よりご覧いただけます。
また、ライトニングトーク中で紹介しているアドオン「システムモニター」の最新版であるVer.0.3は、以下からダウンロードできます。
以下、イベントの感想と、イベント中に語ることができなかった点について書いていきたいと思います。
このセッションでは、次期Firefoxでの標準搭載も視野に入れて開発が進められているJetpackや、新しいUIの可能性を模索するUbiquityの開発者として知られるAza Raskinさんに対して、現在Firefoxアドオンを開発している・開発した経験がある人の立場からの質問を私が、主にWebアプリケーションの開発を行っておりこれからJetpack用のスクリプト(Jetpack feature)の開発に携わる可能性がある人の立場からの質問を天野さんが行う形で、Firefoxの今後とWebアプリケーションの将来像について様々な話を聞かせていただきました。
Firefoxはアドオン(拡張機能)によるカスタマイズ性の高さが最大の特長ですが、自由度の高さゆえに、アドオンの開発には様々な準備と知識が必要になるという欠点もあります。Jetpackは、この欠点をカバーしてFirefoxの機能を拡張するスクリプトをより容易に配布・利用できるようにする物として開発されている、新しいアドオンのメカニズムです。「拡張機能」がテーマである今回のFirefox Developers Conference 2009においては、避けて通れない話題ですね。
今回のトークセッションにあたって事前にいくつかのJetpack featureを開発したのですが、その際に最も強く感じた事は、APIについての不満でした。現状ではAPIに関するドキュメントが圧倒的に不足しており、また、APIがドラスティックに変更されている最中であるため、安定したJetpack featureの開発および利用はまだまだ難しいのが実情です。セッション中でも質問を行いましたが、これについては、むしろ今が開発に口出ししてAPIをより良い物にしていくチャンスと捉えてほしいという回答をいただきました。とはいえ、APIドキュメントの重要性は彼らの間でも認識されており、そのための体制を整えているところであるとの話も聞けました。
Azaさんは、セカンドシステム症候群に陥ってはならないという事を再三述べていました。これまでのアドオンの仕組みに多くの不満があるからといって、その代替として新たに登場してきたJetpackに過剰な要求をして完璧な物を最初から求めると、議論が紛糾するばかりで実装が停滞し、Jetpackという仕組み自体がいつまで経っても完成しなくなってしまいます。ほどほどの所でまずは皆に使ってもらい、その上で実際の利用状況を見ながら少しずつ不足箇所を補っていく、という姿勢が重要ですね。
Jetpack featureで利用可能なライブラリとしては、現在はjQueryが標準添付されていますが、ゆくゆくはPerlにおけるCPANのように、様々なライブラリを簡単に利用できるようにする計画もあるそうです。また、現状のJetpackも将来のバージョンも、jQueryを使うことは必須というわけではなく、ライブラリの機能を使わずにJetpack featureを開発しても問題は無いとのことです。なお、私も天野さんも既存のライブラリをほとんど利用していない理由について、Azaさんより逆に質問をされるという場面もありました。(天野さんの場合は主に既存のライブラリのオーバーヘッドが気になるためで、私の場合はFirefox用アドオンの開発のみを行う都合上、ライブラリの最大のメリットである「ブラウザ間の差異の吸収」の恩恵を受けにくいというのが理由です。)
天野さんがWebアプリの開発者の観点から投げかけられた質問として、WebのUIを良くするにはどのようにすればよいか?という物もありました。これについてAzaさんは、作り手側が絶対に手出し・口出ししないようにして、目の前でエンドユーザに実際に使ってもらいその様子を観察すれば、WebのUIはもっと良くなるだろうとおっしゃっていました。ユーザビリティ・テストはUI開発の基本と言えますが、DTPなど視覚的デザインの文脈で捉えられることがまだまだ多いWebの世界においても、きちんと「インターフェース」としての作り方を意識する必要があるということですね。
国ごとの文化の違いがUIに与える影響について、Azaさんは欧米と中国のWebブラウズの仕方の違いを例に挙げてお答え下さいました。曰く、欧米や日本では1つの画面にかじりついて操作するのが一般的であるのに対し、中国では画面から少し離れて、画面内に非常に多くの機能・要素を詰め込んでそれらを平行して利用する場合が多いそうです(そのため、Googleのかつてのシンプルなトップページは不評だったそうです)。そういった文化ごとの利用形態の違いに合わせるために、末端にいる人達にUIのデザインを行う権限を持たせる必要があるのではないか、という意見も示されていました。
他のブラウザの隆盛、特にWebKitが多数のプロジェクトで採用されている事についての質問もありました。これについては、かつては止まったWebブラウザの進化をFirefoxが刺激して活性化させたように、今はWebKitがWebを活性化させているとして、その結果Webがより便利になるのであれば最終的な勝者は我々エンドユーザだ、との考えをお答えいただきました。
というように、Jetpackの話題のみにとどまらず、WebのUI一般の話にまで言及したトークセッションとなりましたが、皆様お楽しみいただけましたら幸いです。
ライトニングトークへの参加希望者が多かったため、過去に発表経験のある数人については懇親会の場で発表を行うこととなりました。今回はシステムモニターで行った、アドオンによるWebアプリケーション向けAPIの提供方法について発表しました。
「scriptable hardware」というキーワードで、基調講演でChris BlizzardさんがFirefoxの加速度センサー対応やカメラ用APIの実験について語っていらっしゃいました。クリアコードでも独自に同様の研究を行っており、システムモニター以外にも、指紋認証アドオン「Fingerprint-Auth」の実装を進めております。また、その開発の過程で浮上してきたアイデアとして、USB機器全般をJavaScriptから制御できるようにしてみてはどうかという話もあり、それについても「進行中のプロジェクト」としてお話しさせていただきました。Fingerprint-Authについては実際にお試しいただける形の物を近日中に公開する予定ですので、どうぞお楽しみに。
セッション「js-ctypes ~ネイティブコードを呼び出す新しいカタチ」では、JavaScriptからプラットフォームネイティブなバイナリの機能を呼び出すより平易な方法を提供するFirefox 3.6からの新機能js-ctypesを紹介されていましたが、システムモニターやFingerprint-AuthのようにJavaScriptのグローバルな名前空間にAPIを提供しようと思うと、現状ではXPCOMコンポーネントとして実装を行う必要があります。また、XPCOMコンポーネントはC++での開発となるため、各プラットフォーム向けのビルドやnsISecurityCheckedComponentインターフェースの実装などの作業が必要となります。残念ですね。
なお、発表ではすべての実装がJavaScriptによって行われているXUL/MigemoでのAPIの提供方法を例として紹介しました。JavaScript製XPCOMコンポーネントの場合は、比較的簡単に機能をWebアプリケーション向けのAPIとして公開することができます。作成手順についてはFirefox 3 Hacks等の解説と発表資料中の説明を併せてご覧下さい。
株式会社はてなさんのはてなブックマークFirefox拡張についての発表では、開発に使用したツールとして弊社開発のテスティングフレームワークUxUをご紹介いただけました。主にデータベース関連の機能等のユニットテストに利用されているとのことで、非同期処理が絡むテストの開発に役立ったとのお言葉をいただけたのは嬉しかったです。また、私が個人的に開発している各種のアドオンについても、ソースコードをアドオン開発の参考にしていただけたそうで、ありがたい限りです
なお、UxUでは最近のバージョンアップでより使いやすいユーザ操作エミュレーション用のAPIが追加されました。基本部分のユニットテスト以外にも、GUIが関係する機能テストやインテグレーションテストにもUxUを活用していただけたらと思っております。
UbiquityにもJetpackにも共通している1つの狙いとして、「より多くの人が開発を行えるようにする」という目標があるそうです。より簡単に開発できるようになり、より多くの人が開発できるようになれば、より多くのイノベーションが生まれる、というのは前回のFirefox Developers Conferenceの際にもAzaさんが語られていた話ですが、AutoPagerize、ひいてはその原点であるGoogleAutoPagerが、どちらもGreasemonkeyスクリプトという世界から登場したことは、まさにその象徴的な出来事だと思います。
これはFirefoxのアドオン開発のみに限られた話ではなく、Chris Blizzardさんが語ったキーワード「scriptable hardware」もそれに連なる話であると言えるでしょう。折しも、先日GoogleがClosure Toolsという、JavaScriptでのアプリケーション開発のためのツール集を公開しました。APIやツールなど様々な側面から、Webクライアント上で動作する高機能なアプリケーションを開発しやすくするための土壌が整いつつあります。Webアプリケーションがエンドユーザからただの「アプリケーション」として扱われるようになる、Web OSという未来が本当にすぐそこまで来ているのだということを、改めて意識させられたイベントでした。
2009年10月30日付で、テスティングフレームワークUxUのバージョン0.7.5をリリースしました。
UxUはこれまで「Firefoxアドオン開発用テスティングフレームワーク」と銘打っていましたが、Thunderbird用アドオンの開発にも利用されていることと、バージョン0.7.0以降からXULRunnerベースのアプリケーション一般に対してインストール可能なようになったことから、現在のプロジェクトページ上では「Firefox/Thunderbird用アドオン・XULRunnerアプリケーション開発用テスティングフレームワーク」と表記しています。
バージョン0.7.0以降で、UxUはデータ駆動テストの記述に対応しました。今回はUxUでのデータ駆動テストの記述方法の解説を通じて、データ駆動テストの利便性についてご紹介したいと思います。
このエントリ内の目次:
データ駆動テストとは、簡単に言えば、「テストのロジックとデータを分離した自動テスト」の事です。データ駆動テストの利点を理解していただくために、データ駆動テストではないテストの例もいくつか挙げながら、それぞれの利点と欠点を見ていきましょう。
以下は、XUL/Migemoという「ローマ字入力で普通の日本語の検索を行う」アドオンの中の、半角英数字によるローマ字入力をひらがなに変換するモジュールのテストの一部です。
function test_roman2kana() { assert.equals('あいうえお', transform.roman2kana('aiueo')); assert.equals('aiueo', transform.roman2kana('aiueo')); assert.equals('がぎぐげご', transform.roman2kana('gagigugego')); assert.equals('にほんご', transform.roman2kana('nihongo')); assert.equals('ぽーと', transform.roman2kana('po-to')); assert.equals('きゃっきゃ', transform.roman2kana('kyakkya')); assert.equals('うっうー', transform.roman2kana('uwwu-')); assert.equals('\\(\\)\\[\\]\\|', transform.roman2kana('\\(\\)\\[\\]\\|')); assert.equals('\\(\\[', transform.roman2kana('\\(\\[')); assert.equals('\\)\\]', transform.roman2kana('\\)\\]')); assert.equals('\\|', transform.roman2kana('\\|')); }
このモジュールのroman2kana()メソッドは、半角英数字のローマ字入力をひらがなに変換し、それ以外の入力はそのまま返すという仕様になっています。この仕様通りに動作するかどうかを検証するため、ここでは11種類の引数を渡し、戻り値をassert.equals()で検証しています。検証しないといけないパターンが増えた時には、行をコピーして引数と期待値の部分を書き換えることになります。
パッと見て分かるかと思いますが、このテスト用コードには以下のような問題があります。
「テストのロジック」と「テストしなければいけないデータ」が一緒に記述されているため、メンテナンス性が低いコードになってしまっていると言えます。
このようなテストのメンテナンス性を高めるための方法の1つとしては、アサーションを行う部分を関数としてまとめておくというやり方が考えられます。例えば以下の要領です。
function test_roman2kana() { function oneTest(aExpected, aInput) { assert.equals(aExpected, transform.roman2kana(aInput)); } oneTest('あいうえお', 'aiueo'); oneTest('aiueo', 'aiueo'); oneTest('がぎぐげご', 'gagigugego'); oneTest('にほんご', 'nihongo'); oneTest('ぽーと', 'po-to'); oneTest('きゃっきゃ', 'kyakkya'); oneTest('うっうー', 'uwwu-'); oneTest('\\(\\)\\[\\]\\|', '\\(\\)\\[\\]\\|'); oneTest('\\(\\[', '\\(\\['); oneTest('\\)\\]', '\\)\\]'); oneTest('\\|', '\\|'); }
コードの冗長さが減りました。また、メソッド名や引数の取り方が変わった時も、変更が必要な箇所は1箇所だけになりました。
これでも悪くはないのですが、実際にテストを繰り返し走らせていると、以下のような問題が浮き彫りになってきます。
例えば以下のようなシナリオが考えられます。
「3の段階で行った修正で5や8のバグが発生した」という可能性もありますが、最初から2や5や8のバグがあったのであれば、まとめて直せていたかもしれません。これでは、バグを直しても直してもきりがないという、モグラ叩きのような感覚に陥ってしまいます。
メンテナンス性を高める別の手法として、アサーションに使う期待値とメソッドに渡す引数だけを配列で別途定義しておくというやり方も考えられます。
function test_roman2kana() { var patterns = [ ['あいうえお', 'aiueo'], ['aiueo', 'aiueo'], ['がぎぐげご', 'gagigugego'], ['にほんご', 'nihongo'], ['ぽーと', 'po-to'], ['きゃっきゃ', 'kyakkya'], ['うっうー', 'uwwu-'], ['\\(\\)\\[\\]\\|', '\\(\\)\\[\\]\\|'], ['\\(\\[', '\\(\\['], ['\\)\\]', '\\)\\]'], ['\\|', '\\|'] ]; for each (var pattern in patterns) { assert.equals(pattern[0], transform.roman2kana(pattern[1])); } }
これにもやはり問題があります。
1つ目の問題点については前述したとおりです。
2つ目の問題は、この例のように2次元配列を使った場合に現れます。上記の例では配列の0番目の要素が期待値、1番目の要素が入力となっていますが、これではどっちがどっちなのかを常に意識する必要があります。
3つ目は、ループに特有の問題です。UxUではテストにfailした時にスタックトレースが表示され、べた書きした場合や関数を使った書き方の場合であれば、スタックトレースを辿れば「どのパターンで失敗したのか」の情報に辿り着くことができます。しかし、ループを使っていると、スタックトレースの行き着く先はループの中になってしまうため、どのパターンに対して失敗したのかが一目では分からなくなってしまいます。
この対策として、UxUのアサーションでは最後の引数として任意のメッセージを渡せるので、以下のようにして「どのパターンで失敗したのか」を表示させることは可能です。
assert.equals(pattern.expected, transform.roman2kana(pattern.input), pattern.input+'に対するテスト');
しかし、実際にたくさんテストを書くようになってくると、これが地味に面倒です。これが4つ目の問題点です。
べた書きした場合や関数を使った書き方の場合であれば、このような配慮なしに淡々とテストを書いていても、テスト実行時にはスタックトレースを辿ればデバッグに必要な情報を得られます。それなのに、ループに対してはこのような配慮をしなければいけないわけです。この面倒さによって、テストを新しく書いたり過去に書いたテストをメンテナンスしたりする意欲がじわじわと削がれてしまう、というのが一番の問題点だと言えます。
UxU 0.7.0以降で導入されたデータ駆動テストの仕組みを使うと、上記のテストはこのように書くことができます。
test_roman2kana.parameters = utils.readParametersFromCSV('patterns.csv'); function test_roman2kana(aParameter) { assert.equals(aParameter.expected, transform.roman2kana(aParameter.input)); }
テスト用のコードにはロジックだけを書き、データは外部ファイルで定義します(後述しますが、テストケース内にデータを埋め込むこともできます)。データを定義しているファイルの形式はCSVです。
| input | expected | |
| 半角英数 | aiueo | あいうえお |
| 全角英数 | aiueo | aiueo |
| 濁音のみ | gagigugego | がぎぐげご |
| 濁音混じり | nihongo | にほんご |
| 音引き | po-to | ぽーと |
| 拗音 | kyakkya | きゃっきゃ |
| 撥音 | uwwu- | うっうー |
| paren | \\(\\)\\[\\]\\| | \\(\\)\\[\\]\\| |
| parenOpen | \\(\\[ | \\(\\[ |
| parenClose | \\)\\] | \\)\\] |
| pipe | \\| | \\| |
この時、UxUは「test_roman2kana」という1つのテストではなく、「test_roman2kana (半角英数)」「test_roman2kana (全角英数)」……という名前の11個のテストを実行するようになります。
このように、データ駆動テストには多くのメリットがあります。単純な入出力のパターンを数多く検証しなければいけない場面で、データ駆動テストは威力を発揮します。
UxUでは、テスト関数のparametersプロパティに配列またはハッシュを代入すると、そのテストをデータ駆動テストとして実行するようになります。この時テスト関数には引数として、parametersプロパティの配列またはハッシュの要素が1つずつ渡されます。
以下は、配列を指定した場合の例です。
test_someFunc.parameters = [ { expected : '29', input : 'niku' }, { expected : '2929', input : 'nikuniku' }, { expected : '029', input : 'oniku' } ]; function test_someFunc(aParameter) { /* 1回目: aParameter = { expected : '29', input : 'niku' } 2回目: aParameter = { expected : '2929', input : 'nikuniku' } 3回目: aParameter = { expected : '029', input : 'oniku' } */ ... }
ハッシュを指定した場合は、以下のようになります。ハッシュのキーはテスト関数には渡されず、結果を表示する時のテスト名として表示されます。
test_someFunc.parameters = { simgle: { expected : '29', input : 'niku' }, double: { expected : '2929', input : 'nikuniku' }, o: { expected : '029', input : 'oniku' } }; function test_someFunc(aParameter) { /* 1回目: aParameter = { expected : '29', input : 'niku' } 2回目: aParameter = { expected : '2929', input : 'nikuniku' } 3回目: aParameter = { expected : '029', input : 'oniku' } */ ... }
データ駆動テストのサポートに併せて、いくつかの新しいヘルパーメソッドが追加されています。これらを使うことで、データ駆動テストをより簡単に作成・メンテナンスすることができます。
前述の例で使用しているutils.readParametersFromCSV()は、CSVファイルの内容を読み込み、最初の行のカラム名をキーとしたハッシュとして返します。例えば前述の例のCSVは、以下のようなハッシュとして解釈されます。
// test_roman2kana.parameters = utils.readParametersFromCSV('patterns.csv'); // これは、以下のように書くのと同じ test_roman2kana.parameters = { '半角英数': { input: 'aiueo', expected: 'あいうえお' }, '全角英数': { input: 'aiueo', expected: 'aiueo' }, '濁音のみ': { input: 'gagigugego', expected: 'がぎぐげご' }, '濁音混じり': { input: 'nihongo', expected: 'にほんご' }, '音引き': { input: 'po-to', expected: 'ぽーと' }, '拗音': { input: 'kyakkya', expected: 'きゃっきゃ' }, '撥音': { input: 'uwwu-', expected: 'うっうー' }, paren: { input: '\\(\\)\\[\\]\\|', expected: '\\(\\)\\[\\]\\|' }, parenOpen: { input: '\\(\\[', expected: '\\(\\[' }, parenClose: { input: '\\)\\]', expected: '\\)\\]' }, pipe: { input: '\\|', expected: '\\|' } };
CSVファイルはRFC4180準拠の形式の読み込みに対応しています。カンマ区切りではなくタブ区切りのファイルを使用したい場合は、utils.readParametersFromTSV()を使用して下さい。どちらも、読み込みたいファイルのパス(相対パスも利用できます)を第1引数に、ファイルのエンコーディングを第2引数に指定します。エンコーディング指定を省略した場合はUTF-8として読み込みます。
以下のリンク先に、各メソッドの詳しい説明があります。
また、JSON形式で保存した外部ファイルを読み込むためのutils.readJSON()というメソッドもあります。
test_roman2kana.parameters = utils.readJSON('patterns.json');
こちらも、読み込みたいファイルのパス(相対パスも利用できます)を第1引数に、ファイルのエンコーディングを第2引数に指定します。エンコーディング指定を省略した場合はUTF-8として読み込みます。詳しい説明は以下のリンク先をご覧下さい。
データ駆動テストの仕組みを利用すると、テストのロジックとデータを分離できるため、メンテナンス性が高まることが期待できます。様々なパターンの入力を受け付ける機能を開発する時は、データ駆動テストをぜひ一度試してみて下さい。
CPUの使用率をFirefoxのツールバー上に表示するアドオン「システムモニター」のバージョン0.2をリリースしました。以下のリンク先からダウンロードできます。
前のバージョンからの変更点は以下の通りです。
インストールすると、初回起動時に以下のイメージのようなダイアログが表示されます。

「はい」を選択すると、メニューバーの右端(Mac OS Xではナビゲーションツールバーの右端)に、以下のイメージのようにCPU使用率のグラフが表示されます。

プラットフォームは、WindowsXP SP1以降、Mac OS X、Linuxをサポートしています。Linuxではlibgtop2を使用していますので、別途インストールしてください。
このアドオンを導入した環境では、システムモニターが提供するAPIを通じて、Webページ上のスクリプトからシステムモニターと同じ情報を取得できるようになります。以下は、Webページ内にCPU使用率のグラフを埋め込む例です。実際に上記リンク先からアドオンをインストールした状態で、このページを表示してみて下さい。
<div id="system-monitor-demo"></div> <script type="text/javascript"><!-- var container = document.getElementById("system-monitor-demo"); if (!window.system || !window.system.addMonitor) { container.innerHTML = "システムモニターがインストールされていません"; container.style.color = "red"; } else { container.innerHTML = "<div><canvas id='system-monitor' width='300' height='60'></canvas>"+ "<br /><span id='system-monitor-console'></span></div>"; var width = 300; var interval = 1000; var CPUTimeArray = []; var arrayLength = width / 2; while (CPUTimeArray.length < arrayLength) { CPUTimeArray.push(undefined); } // リスナとして登録する関数には、CPUの使用率が割合として渡される。 function onMonitor(aUsage) { var console = document.getElementById("system-monitor-console"); console.textContent = aUsage; CPUTimeArray.shift(); CPUTimeArray.push(aUsage); var canvasElement = document.getElementById("system-monitor"); var context = canvasElement.getContext("2d") var y = canvasElement.height; var x = 0; context.fillStyle = "black"; context.fillRect(0, 0, canvasElement.width, canvasElement.height); context.save(); CPUTimeArray.forEach(function(aUsage) { var y_from = canvasElement.height; var y_to = y_from; if (aUsage == undefined) { drawLine(context, "black", x, y_from, 0); } else { y_to = y_to - (y * aUsage); y_from = drawLine(context, "green", x, y_from, y_to); drawLine(context, "black", x, y_from, y_to); } x = x + 2; }, this); context.restore(); } function drawLine(aContext, aColor, aX, aBeginY, aEndY) { aContext.beginPath(); aContext.strokeStyle = aColor; aContext.lineWidth = 1.0; aContext.lineCap = "square"; aContext.moveTo(aX, aBeginY); aContext.lineTo(aX, aEndY); aContext.closePath(); aContext.stroke(); return aEndY - 1; } // リスナを登録する。 window.system.addMonitor("cpu-usage", onMonitor, interval); window.addEventListener("unload", function() { window.removeEventListener("unload", arguments.callee, false); // ページのアンロード時にリスナの登録を解除する必要がある。 window.system.removeMonitor("cpu-usage", onMonitor); }, false); } // --></script>
上の例をご覧いただいても分かるとおり、このアドオンは、Webアプリケーション(Webページ上のスクリプト)から各種ハードウェアデバイスへアクセスする技術のデモンストレーションとして開発されています。
Webカメラとの連動によるAR(拡張現実)や、指紋認証、Felica認証といった特殊なハードウェアを必要とする認証技術など、この技術を応用することにより今までのWebアプリケーションではできなかった様々なことが可能となります。
クリアコードでは、このようにWebとハードウェアを直結する技術の開発に取り組んでおります。特殊なデバイスを搭載したハードウェアへのWebブラウザエンジンの組み込みや、特殊なデバイスの利用を前提としたWebベースのシステム開発など、ご用命がございましたらぜひinfo@clear-code.comまでお問い合わせください
CPUの使用率をFirefoxのウインドウ右上に表示するアドオンをリリースしました。ダウンロードはこちらからどうぞ。新しいバージョンがすでに公開されていますので、そちらを参照して下さい。
インストールすると、以下のイメージのようにウインドウ右上に直近のCPU使用率がグラフで表示されます。

プラットフォームは、WindowsXP SP1以降、Mac OS X、Linuxをサポートしています。Linuxではlibgtop2を使用していますので、別途インストールしてください。
また、このアドオンではCPU使用率をWebアプリケーションからも使えるようにしてあります。JavaScriptで以下のように書くと、intervalTime間隔ごとにfunctionが呼び出され、aUsageにその時のCPU使用率が渡ってきます。
system.addMonitor("cpu-usage", function(aUsage){}, intervalTime);
クリアコードでは、このようにWebアプリケーションから各種ハードウェアデバイスにアクセスする機能の開発を行っております。ご用命はinfo@clear-code.comまでお問い合わせください
2009年6月1日時点でのWindows Vista上でWindows Mobile用Fennecをビルドする方法を紹介します。時間が経つとビルド方法が変わると思うので、注意してください。
Fennecのビルド方法に関する情報はMobile/Build/Fennec - MozillaWiki(英語)にあります。最新情報が必要な場合はこのWikiを見てください。
ただし、Wiki上の情報が古いことがあるので注意が必要です。
Fennecをビルドするために必要なツールをインストールします。 (参考: Mobile/Build/Windows Mobile PrepForBuild - MozillaWiki)
まず、Microsoftが提供しているツールをインストールします。
次に、Mozillaが提供しているビルドツールをインストールします。
このビルドツールの中にはMSYSやMercurialなどのツールが入っています。
「c:\mozilla-build\start-msvc9.bat」というバッチファイルがインストールされます。このバッチファイルを実行することにより、bashが起動し、Fennecビルド用環境に入ることができます。
ビルドに必要なツールが揃ったので、それらのツールを使ってFennecをビルドする方法を説明します。(参考: Mobile/Build/Windows Mobile BuildingIt - MozillaWiki)
FennecのソースコードはMercurialで管理されています。まず、Mercurialでソースコードを取得します。ここからはstart-msvc9.batで起動した環境の中で作業します。
ソースコードは「c:\hg\」以下に置くことにします。
$ mkdir /c/hg/ $ cd /c/hg/ $ hg clone http://hg.mozilla.org/mozilla-central $ cd mozilla-central $ hg clone http://hg.mozilla.org/mobile-browser mobile
ソースコードを取得したらビルド用の設定ファイル「.mozconfig」を作成します。.mozconfigは雛形となるファイルを少しずつ変更して作成するとよいでしょう。ここでは、クリアコードが公開している雛形を利用します。
$ wget -O .mozconfig http://www.clear-code.com/repos/svn/fennec/wince.mozconfig
現在、リポジトリ内にデフォルトの.mozconfigを入れてはどうか、という話がでている(Bug 479515 – push default mozconfig to mobile-browser repo)ので、将来的には取得したソースコード中から雛形を利用できるようになることでしょう。
.mozconfigを作成したら、以下のコマンドでビルドできます。
$ time make -f client.mk build
マシンの速度にもよりますが、1時間以上かかるでしょう。timeはビルド時間を計測するためにつけているだけなので、以下のようにtime無しでビルドすることもできます。
$ make -f client.mk build
ビルドの完了を待つ間に、最新版に追従する方法を説明します。
Mozillaのソースコードは毎日アップデートされています。最新版のソースコードに追従するためには、ソースコードがあるディレクトリで以下のコマンドを実行します。
$ hg pull && hg update && (cd mobile && hg pull && hg update)
このコマンドを実行することにより、手元のソースコードが最新版になります。
アップデートされたソースコードでビルドしなおす場合も最初のビルドのときと同じコマンドを使います。
$ time make -f client.mk build
マシンの速度やソースコードがどの程度アップデートされたかにもよりますが、30分以上かかるでしょう。ビルドの完了を待つ間に、Fennecのデバッグ方法を説明します。
Fennecのデバッグ方法を説明します。(参考: Mobile/Build/Windows Mobile DebuggingIt - MozillaWiki)
まず、Visual StudioでFennecデバッグ用のプロジェクトを作成します。プロジェクトの種類は「Visual C++→スマートデバイス→Win32スマートデバイスプロジェクト」を選んでください。このプロジェクトはデバッガを使うためだけのプロジェクトなので設定はデフォルト値で問題ありません。
プロジェクトを作成したらプロジェクトのプロパティで以下を設定します。
設定したらF5でデバッグを開始します。エミュレータが起動するので、Fennecのビルド結果を出力したディレクトリをエミュレータの「ストレージカード」としてアクセスするための設定をします。この設定は1回行えば今後はその設定を利用できます。
「ストレージカード」としてアクセスするためには、エミュレータのウィンドウメニューから「ファイル→構成→全般→共有フォルダ」とアクセスします。「共有フォルダ」に「c:\hg\fennec-debug\mobile\dist\bin」を指定してください。
これで、エミュレータ上で「\Storage Card\fennec.exe」としてFennecを実行できるようになります。もう一度、Visual Studio上でF5を押してデバッグを開始してください。デバッガ上でFennecが起動します。
注: 現在のFennecは初回起動時(プロファイルがない状態で起動した場合)のみ内部で再起動しています。そのため、再起動した段階でデバッガからデタッチしてしまいます。2回目以降の起動ではそのままデバッガ上で動くので、一度タスクマネージャからFennecを終了してF5でデバッグを開始しなおしてください。
エミュレータでネットワークに接続するためにはVirtual PCをインストールする必要があります。
Virtual PCをインストールするとエミュレータでネットワークを利用できるようになります。エミュレータのウィンドウメニューから「ファイル→構成→NE2000 PCMCIAネットワークアダプタを有効にし、次の項目にバインドする」とアクセスします。この項目にチェックを入れるとエミュレータからネットワークにアクセスできるようになるので、エミュレータ上のFennecからWebを閲覧することができるようになります。
Windows Mobile 6.1.4用のエミュレータは英語用のため日本語フォントが入っていません。日本語を表示するためには、Fennecでの日本語表示設定のように別途日本語フォントを入れる必要があります。
エミュレータ上でのFennecのデバッグについて説明したので、次に、実機へFennecをインストールする方法を説明します。
雛形.mozconfigではデバッグ用ビルドの設定が有効になっています。しかし、実機へインストールする場合は最適化されたリリース用ビルドの方がよいでしょう。インストール方法を説明する前に、まず、リリース用ビルドの設定を説明します。
雛形.mozconfigの中に以下のような記述があります。
ac_add_options --enable-debug ac_add_options --disable-optimize mk_add_options MOZ_OBJDIR=@TOPSRCDIR@/../fennec-debug #ac_add_options --enable-optimize #mk_add_options MOZ_OBJDIR=@TOPSRCDIR@/../fennec-release
この部分のコメントアウトされていない部分がデバッグ用ビルドの設定で、コメントアウトされている部分がリリース用ビルドの設定です。つまり、リリース用ビルドにする場合は以下のように変更します。
#ac_add_options --enable-debug #ac_add_options --disable-optimize #mk_add_options MOZ_OBJDIR=@TOPSRCDIR@/../fennec-debug ac_add_options --enable-optimize mk_add_options MOZ_OBJDIR=@TOPSRCDIR@/../fennec-release
この.mozconfigを使ってビルドすることにより最適化されたリリース用ビルドを作成することができます。
それでは、リリース用ビルドを使ってWindows Mobile用のインストーラを作成する方法を説明します。
Windows Mobile用のインストーラである.cabファイルを作成するには、「$(MOZ_OBJDIR)/mobile/」ディレクトリ($(MOZ_OBJDIR)は.mozconfigで指定したビルド結果出力ディレクト リ)で以下のコマンドを実行します。
$ make installer
雛形.mozconfigのように
mk_add_options MOZ_OBJDIR=@TOPSRCDIR@/../fennec-release
としていた場合は、以下のようになります。
$ cd ../fennec-release/mobile $ make installer
コマンドが完了すると「$(MOZ_OBJDIR)/mobile/dist/」以下にfennec.1.0a1.en-US.wince-arm.cabファイルができます。(ファイル名の中の「1.0a1」はバージョン番号でバージョン毎に異なります。)このファイルをActiveSyncなどで実機へコピーしてインストールすることができます。
Windows Vista上でFennecのビルド・デバッグ・インストーラ作成を行う方法を紹介しました。
これで環境環境は用意できると思うので、あとは「開発合宿後も、Fennec の開発活動に関わる意思を有する方」という条件を満たせば、Mozilla Japan主催のMobile Firefox開発合宿に参加できます。興味のある方は参加してはいかがでしょうか。
2月26日のエントリのエントリでお伝えした通り、以前のWindows Mobile版Fennecには日本語表示に致命的なバグが存在していましたが、弊社エンジニアがこれを修正し、本家にも既にこの修正が取り込まれております。このため、現在mozilla.orgで公開されているNightlyビルドでも、バイナリを修正することなく日本語を表示することが可能です。 ただし、現在のバージョンではまだフォントの自動選択処理に不具合があるため、設定を正しく行っていない場合、インストールしたフォントや訪れたサイトによっては、文書の一部あるいは全てが文字化けする可能性があります。 そこで今回は、4/28現在のNigtlyビルドにおいて日本語を正しく表示する設定を紹介致します。
mozilla.orgのFTPサイトから最新のcabファイルを取得し、インストールします。その後、一度Fennecを起動して、プロファイルを作成します。プロファイルは \Application Data\Mozilla\Fennec\Profiles\xxxxxxxx.default フォルダ以下に作成されます。この後、設定ファイルを手動で変更しますので、一旦Fennecを終了します。
現在のFennecは、Windows Mobile日本語版に標準で搭載されているAC3形式のフォントに対応していません。このため、別途日本語TTFフォントをインストールする必要があります。ここでは例として、VLゴシックをインストールします。
プロファイルフォルダ以下のprefs.jsファイルを開いて、以下ような記述を追加します。
user_pref("font.language.group", "ja");
user_pref("font.name.sans-serif.ja", "VL PGothic");
user_pref("font.name.sans-serif.x-unicode", "VL PGothic");
user_pref("font.name.sans-serif.x-western", "VL PGothic");
user_pref("font.name.serif.ja", "VL PGothic");
user_pref("font.name.serif.x-unicode", "VL PGothic");
user_pref("font.name.serif.x-western", "VL PGothic");
user_pref("intl.accept_languages", "ja");
user_pref("intl.charset.default", "UTF-8");
user_pref("intl.charset.detector", "ja_parallel_state_machine");
TrueTypeフォントであれば、VLゴシックに限らずどのフォントでも使用可能と思われます。例えばIPA Pゴシックを使用する場合は、VL PGothicという記述をIPAPGothicに変更します。
以上でほぼ日本語の表示は可能なのですが、これでもなおフォーム内の文字が化ける場合があります。今回はユーザーCSSでフォントを指定して、この問題を回避します。 プロファイルフォルダ以下に chrome というフォルダを作成し、userContent.css という名前で以下のような内容のファイルを作成します。
input[name], input[value], select[name], option, textarea, button, fieldset, label, legend, optgroup[label] {
font-family: 'VL PGothic', sans-serif;
font-size: 12px;
}
以上で、フォームでも日本語が表示されるようになります。
Fennecで日本語を表示することはできたでしょうか? クリアコードでは、上記のような煩わしい設定を行わなくともFennecで日本語を表示できるよう、引き続き改善作業を行っています。また、現在のNightlyビルドにはまだ含まれていないものの、日本語入力時の未確定文字列が表示されない問題等についても修正を行い、本家にフィードバックしています。
何度かここにも書いてあるように、クリアコードはWindows Mobile上で動作するブラウザFennecの改善に力をいれています。
改善した結果は積極的に本家にフィードバックしており、次のFennecのリリースには取り込まれる予定です。具体的には、以下の点が改善されます。
フォントまわりの改善で、日本語の表示問題が修正されたり、表示速度が高速化されたりします。起動まわりの改善で、使い勝手がよくなったりします。
MozillaサポートやMozilla本体・拡張機能の開発に関心のある方はinfo@clear-code.comまでお問い合わせ下さい。
すでにお気づきの方もいるかもしれませんが、先日から、クリアコードで開発したプログラムが入ったSubversionリポジトリ(リポジトリの更新状況のRSS)の公開を始めました。
クリアコードでは既存のフリーソフトウェアの開発に参加したり、新しくmilter managerなどのフリーソフトウェアを開発したりしていますが、それらの開発成果の公開場所はケースバイケースとなっています。
このように、クリアコードの開発成果のソースコードは様々なホスティングサイトのリポジトリにて管理、および公開されています。
まもなくクリアコードは設立から3年が経とうとしていますが、その間、プロジェクトを作るまでもないような小規模なソースコードがいくつかたまってきました。この度、そのようなソースコードをSubversionリポジトリで公開することにしました。
このリポジトリには現在、ページの一部を折りたたむfolding.jsや、このククログを生成するためのtDiary関連のスクリプト(日記のデータをSubversionで管理するIOバックエンドや日記を静的なHTMLに変換するスクリプトなどの記事で述べた物)、Thunderbird用の各種アドオンのソースコードが入っています。誰でも自由にチェックアウトできますので、注意事項をご了承の上でどうぞご利用ください。
以下、現在入っているプログラムを簡単に紹介します。
ククログだけではなく、milter managerのブログでも使っています。使い方はmilter managerのtdiary.confが参考になると思います。
Thunderbird Add-ons - クリアコードで公開している、Thunderbirdのバグを回避するパッチや挙動の変更を行う拡張機能です。公開ページにも書いてありますが、これらの拡張機能は無保証です。業務上の必要性からの導入をお考えの場合は、Mozilla Firefox & Mozilla Thunderbird保守・サポートサービスのご利用もご検討ください。
リポジトリの更新状況を配信しているRSSは、Subversionに標準添付のcommit-email.rbで生成しています。今回、RSSのタイトルや説明を日本語にしたかったので、Subversionのtrunkに--rss-titleと--rss-descriptionオプションを追加しました。また、--repository-uriで指定されたリポジトリのURIをコミットメールのX-SVN-Repositoryヘッダに設定するようにもしています。
Subversionリポジトリの整形表示にはRepos Styleを利用しています。mod_dav_svnにはSVNIndexXSLTというオプションがあって、それを利用しています。
みなさん、テストしてますか?(挨拶)
UxUでは、テスト失敗時に表示されるスタックトレースからテキストエディタを起動することができます。この時、利用するテキストエディタがコマンドライン引数による行指定に対応していれば、エラーが発生した行を直接開いて編集できます。きちんと設定しておけば、テストを実行して、編集して、またテストして、といったサイクルで開発を進められるので非常に便利です。
以下に、有名なテキストエディタ向けの設定の例をいくつか挙げてみました。UxUの設定ダイアログの「MozUnitテストランナー」タブでエディタ起動用のコマンドとして入力してください。(エディタの実行ファイルのパスは必要に応じて読み替えてください)
なお、%Lは行番号、%Cは列番号、%Fはファイルのパスへと、それぞれ自動的に置換されます。
1月7日のエントリでもお伝えしたとおり、クリアコードでは独自にWindows Mobile版Mozillaの改善に取り組んでいます。
先日リリースされたFennecのプレアルファ版には、日本語が全く表示されないという問題がありました。オープンソースカンファレンス2009 Tokyo/Springにおいて、弊社ブースにてこの問題の修正を目指したライブ開発を行っておりましたが、それから約1週間かかって、ようやく日本語が表示できる所までこぎつけました。
ただし、現在のFennecはAC3形式のフォントには対応しておりません。Windows Mobile日本語版のデフォルトのフォントはこのAC3形式であるため、日本語を表示するためには、別途TrueTypeフォントをインストールする必要があります。
今回のバグは、Fennecがインストールされているフォントを認識できず、結局システムフォントしか利用されないことが原因でした。このため、日本語環境のみならず、他のどの環境においても、システムフォント以外のフォントに変更することができない状態となっておりました。
現在Mozillaの開発コミュニティにおいて、Fennecと呼ばれるモバイルデバイス向けのウェブブラウザが開発されていることをご存じの方も多いかと思います。Fennecでは、PC版のFirefoxと同じGeckoエンジンを使用することによってPC上と同じレンダリング結果が得られるほか、拡張機能もサポートされることがアナウンスされており、モバイルデバイス用ウェブブラウザに新たな変革をもたらす事が期待されています。
Fennecのターゲットプラットフォームとしては、Linuxの他に、Windows Mobileも挙げられています。MozillaのMercurialリポジトリで開発履歴を確認すると、Windows Mobile版Mozillaは、主にBrad Lassey氏とDoug Turner氏の2人によって活発に開発が進められているようです。
しかしながら、現在のところ一般向けにモバイルデバイス用実行ファイルが 提供されているのはNokia N810用のみであり、Windows Mobile用はアルファ版すら提供されていない状況です。
N810が日本では未発売ということも相まって、Windows Mobile版のMozillaがどの程度動作するものなのか、やきもきされている方もおられるのではないでしょうか?
現在、クリアコード社内においても組み込み機器向けMozillaの研究を進めており、Windows Mobile用XULRunner のビルド及び動作も確認しておりますので、そのような方々に向けて、開発版の動作状況を動画で紹介したいと思います。
ただし、この動画で動作しているウェブブラウザはFennecではなく、MyBrowserという動作確認用の簡素なサンプルブラウザです。また、動画で使用しているバージョンは2008年12月中旬頃のものを元にしており、さらに、最低限の動作を確認できるよう弊社内で独自の修正を加えているため、現在リポジトリから取得できるバージョンとは少し状況が異なります。
本動画は、リモートの開発環境からデバッグ版MyBrowserを起動した直後から開始されています。ブラウザ画面が表示されるまで20秒以上と、起動がやや遅い印象はありますが、一度起動してしまうと、思いのほかスムースにレンダリングされているように見受けられます。
まだまだモバイルデバイス向けの最適化が進んでいないため、単純に他のモバイルデバイス用ウェブブラウザと比較してしまうと苦しい面もありますが、現時点でも十分実用に成り得るポテンシャルがあることは確認していただけるかと思います。
弊社では、Fennecについても同様にWindows Mobile上での動作を確認しています。

しかし、こちらはまだWindows Mobileデバイスに合ったレイアウト調整やパフォーマンス調整が行われておらず、動画で紹介するには時期尚早であるため、今回はデモを見送りました。FennecについてはPCで動作するバージョンがリリースされているため、こちらを試用して夢を膨らませておくのが良いでしょう。
ククログでは、今後とも随時モバイル版Mozillaの研究・開発状況をレポートしていく予定です。
Firefoxアドオン開発者向け自動テストツールのUxUは、新たに発見したバグの修正にも活用することができます。本日リリースされたXUL/Migemo バージョン0.11.7で行われた修正の場合を例に、実際のデバッグ作業の流れを解説します。
XUL/Migemoは、Firefoxで表示しているページ内のテキストを検索する機能を提供するアドオンですが、検索を開始する際に、「現在のスクロール位置から検索を開始する」という処理を含んでいます。0.11.6以前では、この機能を使用している時に、ページ先頭から検索が始まるべき場面で、先頭以外の場所から検索が始まってしまうことがあるという問題が起こっていました。
いくつか条件を変えて調査した結果、スクロールが発生しているページでは期待通りの結果になっているのに対して、スクロールが全く発生していないページ(ページ全体がウィンドウの現在の大きさの中に収まっているページ)では期待と異なる結果になっていることが判明しました。
前述の処理の肝となっているのは、pXMigemoFindクラスのfindFirstVisibleNode()というメソッドです。このメソッドは、渡されたフレーム(DOMWindow)において現在のスクロール位置で見えている最初の要素を検索して返す物です。このメソッドの戻り値を確認した所、前述の条件下では戻り値が期待と異なっている事が判明しました。
このことから、今回主な修正対象になるのはこのfindFirstVisibleNode()というメソッドであるということになります。
上記メソッドの実装を見直す前に、UxU用のテストケースを作成します。これにより、これから行う修正で目指すべきゴールが明確になります。つまり、このテストが成功する状況まで持って行くことが、今回の修正のゴールとなります。
テストケースはJavaScriptのコードだけで完結する場合もありますが、今回のような場合は実際のWebページを使ってテストを行う必要があります。そこで、問題が発生する条件と発生しない条件の両方の事例としてHTMLドキュメントを用意します。
これらのドキュメントを使い、shortPage.htmlではスクロールが発生せずlongPage.htmlではスクロールが発生する、という条件の下でテストを行うテストケースをこれから作成することになります。
ところで、現在の実装で問題が起こっている場合だけでなく、すでに正常に動いている場合の事例も同時に作成していることに気がついたでしょうか? 両方の場合を常にテストすることで、「ある問題を修正したら、今度は、今までは正常に動いていた物が動かなくなった」という状況、いわゆる後退バグを未然に防ぐことができます。 *1
それではテストケースを作成します。
utils.include('pXMigemoClasses.inc.js'); var findModule; function setUp() { yield utils.setUpTestWindow(); findModule = new pXMigemoFind(); findModule.target = utils.getTestWindow().gBrowser; } function tearDown() { findModule.destroy(); findModule = null; utils.tearDownTestWindow(); } function testFindFirstVisibleNode() { // ここに実際のテスト内容を記述する }
pXMigemoFindクラスの単体テストはまだ作成されていなかったので、今回はsetUpとtearDownから作成します。すでに作成済みのテストケースがあり、それにテスト項目を追加する場合、この作業は不要となります。
pXMigemoFindクラスはtabbrowser要素を用いて初期化する必要があるため、setUpでテスト用のFirefoxウィンドウを開き、そのウィンドウのtabbrowser要素で初期化します。また、tearDownではsetUpで開いたテスト用のFirefoxウィンドウを閉じて、pXMigemoFindクラスのインスタンスを破棄します。UxUは関数名を見て自動的にその種類を判別するため、これだけで、これらの関数はテストの初期化処理と終了処理として認識されるようになります。
なお、インクルードしているpXMigemoClasses.inc.jsというファイルは、pXMigemoFindクラスやそのクラスが依存しているすべての関連クラスの定義を読み込む物です。
次に、テストの内容を作成していきます。
function testFindFirstVisibleNode() { var win = utils.getTestWindow(); win.resizeTo(500, 500); assert.compare(200, '<', utils.contentWindow.innerHeight); // ここに実際のテスト内容を記述する }
関数名を「test」で始めると、その関数はテストの内容として自動的に認識されます。
最初に、ウィンドウの大きさを調整して、「shortPage.htmlではスクロールが発生せずlongPage.htmlではスクロールが発生する」という条件を整えておきます。ここでは、テスト自体が期待通りの条件下で実行されていることを確認するために、assert.compare()でテスト用フレームの大きさを調べています。
yield utils.addTab(baseURL+'../res/shortPage.html', { selected : true }); var frame = utils.contentWindow; var node = findModule.findFirstVisibleNode(findModule.FIND_DEFAULT, frame); assert.equals(utils.contentDocument.documentElement, node); item = frame.document.getElementById('p3'); node = findModule.findFirstVisibleNode(findModule.FIND_BACK, frame); assert.equals(item, node);
テスト用のドキュメントを新しいタブで開き、findFirstVisibleNode()メソッドの返り値が期待通りかどうかを検証します。1つ目の検証は前方検索、2つ目は後方検索です。
同様にして、スクロールが発生する場合のテストも作成します。
yield utils.addTab(baseURL+'../res/longPage.html', { selected : true }); frame = utils.contentWindow; node = findModule.findFirstVisibleNode(findModule.FIND_DEFAULT, frame); assert.equals(utils.contentDocument.documentElement, node); item = frame.document.getElementById('p10'); frame.scrollTo(0, item.offsetTop); node = findModule.findFirstVisibleNode(findModule.FIND_DEFAULT, frame); assert.equals(item, node); frame.scrollTo(0, item.offsetTop - frame.innerHeight + item.offsetHeight); node = findModule.findFirstVisibleNode(findModule.FIND_BACK, frame); assert.equals(item, node); item = frame.document.getElementById('p21'); frame.scrollTo(0, item.offsetTop - frame.innerHeight + item.offsetHeight); node = findModule.findFirstVisibleNode(findModule.FIND_BACK, frame); assert.equals(item, node);
スクロールされていない時、ページ途中までスクロールされている時、ページの最後までスクロールされている時の各ケースで前方検索と後方検索を行い、結果を検証します。
ここで、かなりの部分のコードが重複していることに気がついたでしょうか。このような場合、それぞれの検証の前で重複しているコードと検証とをひとまとめにして実行する関数(カスタムアサーション)を定義しておくと、テスト項目の追加が簡単になります。以下は、カスタムアサーションを使ってここまでのテスト内容を書き直した物です。
function testFindFirstVisibleNode() { var win = utils.getTestWindow(); win.resizeTo(500, 500); assert.compare(200, '<', utils.contentWindow.innerHeight); function assertScrollAndFind(aIdOrNode, aFindFlag) { var frame = utils.contentWindow; var item = typeof aIdOrNode == 'string' ? frame.document.getElementById(aIdOrNode) : aIdOrNode ; frame.scrollTo( 0, (aFindFlag & findModule.FIND_BACK ? item.offsetTop - frame.innerHeight + item.offsetHeight : item.offsetTop ) ); var node = findModule.findFirstVisibleNode(aFindFlag, frame); assert.equals(item, node); } yield utils.addTab(baseURL+'../res/shortPage.html', { selected : true }); assertScrollAndFind(utils.contentDocument.documentElement, findModule.FIND_DEFAULT); assertScrollAndFind('p3', findModule.FIND_BACK); yield utils.addTab(baseURL+'../res/longPage.html', { selected : true }); assertScrollAndFind(utils.contentDocument.documentElement, findModule.FIND_DEFAULT); assertScrollAndFind('p10', findModule.FIND_DEFAULT); assertScrollAndFind('p10', findModule.FIND_BACK); assertScrollAndFind('p21', findModule.FIND_BACK); }
テストケースが完成したら、テストを実行してみましょう。実装の修正前なので、当然、このテストは「失敗」という結果が出ます。ですが、この段階では問題ありません。これから、このテストの結果が「成功」になることを目指して実装を修正していきます。
実装の修正内容については省略します。良いアイディアを思いついたら、それを実装に反映して、再度テストを実行してみましょう。テストに成功しないようであれば、まだ修正が必要です。
何度テストを実行しても結果が「成功」になるようになれば、実装の修正はひとまず完了です。修正内容をリポジトリにコミットするなり、修正済みの新しいバージョンとしてリリースするなりしましょう。
以上で、今回発見された問題の修正は完了しました。
しかし、上記のテストだけではテストしきれないような、より複雑な条件でだけ発生するバグが新たに見つかるかもしれません。そのような場合は、テストを新たに追加して、それらがすべて「成功」するようになるまで修正してやりましょう。その時はもちろん、他のテストも同時に実行することを忘れないようにしましょう。
また、開発を進める中で、他の部分に加えた変更の影響を受けて上記のテストが失敗するようになることがあるかもしれません。そのような場合、再びテストが通るようになるように実装を修正する必要があります。
実装の仕様を変更した時にも、ここで作成したテストケースは「成功」しなくなる場合があります。このような場合は「ゴール」自体が変わったということになりますので、実装ではなくテストケースの側を修正しなくてはなりません。
自動テストを使った開発では、メンテナンスする必要があるコードが「実装」と「テストケース」の2つになるため、一見すると、手間だけが倍増するように思えるかもしれません。
しかし、一連のテスト手順を自動化しておくことで、人の手によるテストでは見落としてしまいかねない思わぬ後退バグの発生に迅速に気づけるようになります。後退バグの発生に日々頭を悩ませている人は、是非、自動テストを開発に取り入れてみてください。
*1 もちろん、後退バグの発生自体は未然には防ぎきれません。しかし、後退バグの発生にすぐ気がつくことができれば、コミットやリリースの前にその後退バグを修正できるため、他の共同開発者やユーザには後退バグの影響を与えずに済むようになります。
多数のユーザを抱える企業や大学などでは、ユーザの情報をディレクトリサーバに保存していることが多いと思われますが、Thunderbirdには、そのようなディレクトリサーバにLDAP(Lightweight Directory Access Protocol)で接続して必要な情報を取得する機能があります。具体的には、Thunderbirdでは以下の場面でLDAPが利用できます。
これらの機能をテストするためには、当然ながらディレクトリサーバを用意する必要があります。ここでは、インストールが簡単な「ApacheDS」を利用して、Windows XP環境下でThunderbirdでのLDAP関連機能をテストする手順を解説します。
ApacheDS(Apache Directory Server)は、Webサーバ「Apache」で有名なApache Foundationで開発されているオープンソースのディレクトリサーバです。Windows用にはインストーラが用意されており、特別な知識が無くてもインストールしてすぐに利用を始められます。
Apache Directoryプロジェクトではディレクトリサーバの内容をGUIで管理する「Apache Directory Studio」というEclipseベースのツールも開発されています。これを使って、テストのためのダミーのユーザ情報をディレクトリサーバに登録します。
6から10の操作を繰り返して、さらに何人分かユーザ情報を登録します。「uid」はユーザの識別子となりますので、他と重複しない値を入力してください。
なお、9のステップで「RDN:」欄の右にある「+」ボタンをクリックして入力欄を増やすと以下の操作が期待通りには動かなくなる場合がありますので、ここでは必ず「uid」のみを設定して下さい。
ここまでで入力した情報を、Thunderbirdで実際に利用してみましょう。
これで、Thunderbirdでディレクトリサーバに接続する準備が整いました。
試しに、ディレクトリサーバの内容をアドレス帳として表示してみましょう。アドレス帳ウィンドウの左のアドレス帳一覧から「ApacheDS」を選んで、ウィンドウ右上の検索欄に先ほど登録したダミーのユーザの名前やメールアドレスの一部を入力します。ディレクトリサーバに接続するためのパスワードの入力を求められますので、「secret」(ApacheDSに最初から用意されているサンプル設定のディレクトリサーバのパスワードです)と入力すると、ダミーのユーザ情報のうち今入力した文字列にヒットした物がリストアップされます。また、新規にメール作成ウィンドウを開き、宛先の入力欄に先ほど登録したユーザの名前の先頭数文字を入力すると、該当するユーザのメールアドレスが補完候補として表示されます。
以上で、基本的な手順の解説は終わりです。LDAP関連の各種の隠し設定をテストする場合などに利用してみると良いでしょう。
Firefox用アドオンやXULRunnerアプリケーションなどのいわゆるXULアプリケーションは、ロジック部を主にJavaScriptで記述するため、script.aculo.usのテスト関連機能などJavaScript用のテストツールを使って自動テストを行えます。しかし、一般的なJavaScript用のテストツールはWebアプリケーションをテストすることを主眼において開発されているため、利用できる機能に制限があったり、HTMLではなくXULを使用するXULアプリケーションのテストでは不具合が生じたりする場合があります。
UxU(UnitTest.XUL)は、著名なXULアプリケーション開発支援ツールであるMozLabをベースにクリアコードで開発を行っている自動テスト実行ツールです。FirefoxやThunderbirdなどのXULアプリケーション上での利用を前提としているため、前述のような制限や問題を気にすることなく自動テストを記述できる、便利なヘルパーメソッドを利用できる、などの特長があります。
テストの記述方法やヘルパーメソッドの一覧はUxUの紹介ページに情報がありますが、ここではFirefox用アドオンのXUL/Migemoのテストを実例として示しながら、UxUによる自動テストの方法について簡単にご紹介をしたいと思います。Subversionのリポジトリ内にテストのサンプル用に用意されたタグがありますので、まずはこちらから必要なファイル一式をチェックアウトしておいてください。
まずは最も簡単な例として、「tests」→「unit」とフォルダを辿った中にあるdocShellIterator.test.jsを見てみましょう。
XUL/MigemoはFirefoxのページ内検索を拡張するアドオンですので、フレーム内に検索語句が見つからなかったときは、子フレームや親フレームを検索対象として自動的に再検索を行うといった処理が必要になります。docShellIterator.test.jsでは、このための処理を担当するクラス「DocShellIterator」が正しく機能するかどうかをテストしています。
utils.include('../../components/pXMigemoFind.js', null, 'Shift_JIS');
冒頭では、ヘルパーメソッドのutils.includeを使って、DocShellIteratorクラスが定義されているファイルpXMigemoFind.jsの内容を取り込んでいます。第3引数で読み込むファイルのエンコーディングを指定していますが、これはファイルの中に含まれる日本語のコメントがShift_JISになっているためです。
なお、何らかの事情でそのままファイルをincludeできない(includeするとまずい)場合には、ファイルの内容をテキストとして読み込んで加工した後に評価するという方法もあります。上の例は、以下のように書き換えても同様に動作します。
var extract; eval('extract = function() { '+ utils.readFrom('../../components/pXMigemoFind.js') + '; return DocShellIterator }'); var DocShellIterator = extract();
utils.readFromはファイルの内容を文字列として返しますので、replaceなどを使って邪魔な部分を消してやれば、そのままではincludeできないファイルから必要な部分だけを取り出して評価できます。
このファイルにはテストケースが一つだけ定義されています。テストの前処理(setUp)と後処理(tearDown)は以下の通りです。
var DocShellIteratorTest = new TestCase('DocShellIteratorのユニットテスト'); DocShellIteratorTest.tests = { setUp : function() { yield Do(utils.loadURI('../res/frameTest.html')); }, tearDown : function() { iterator.destroy(); yield Do(utils.loadURI()); },
setUpとtearDownは、それぞれのテストを実行する前と後に毎回実行されます。このテストケースでは、テスト実行前に「テストに使用するフレームにHTMLファイルを読み込む」という処理を行い、テスト終了後に「フレームに空のページを読み込んで内容を破棄する」という処理を行うことで、毎回必ずクリーンなテスト環境を準備するようにしています。
setUpの中では、「フレームに指定したページを読み込み、読み込みが完了するのを待って次に進む」といった処理待ちを行うために、ヘルパーメソッドとyield式を使用しています。yieldは本来はJavaScript 1.7で導入されたジェネレータのための物ですが、UxUではこれを応用して処理待ちを実現しています。JavaScriptで処理待ちというと、タイマーやonloadのようなイベントハンドラを使う方法が真っ先に思い浮かぶと思いますが、yield式を使用すれば、それらの場合に比べて処理の流れをフラットに記述することができます。
個々のテストの定義を見てみましょう。以下のテストでは、DocShellIteratorクラスのインスタンスを生成して、検索対象のフレームを移動する処理を実際に行い、処理結果が期待されたものと等しいかどうかをテストしています。
'前方検索': function() { // 1番目のフレームを初期状態としてインスタンス生成。 iterator = new DocShellIterator(content.frames[0], false); // 初期化は成功したか? assert.initialized(iterator, content.frames[0]); // 次のフレームに移動するメソッドを実行。 assert.isTrue(iterator.iterateNext()); // フォーカスは正常に2番目のフレームに移動したか? assert.focus(iterator, content.frames[1]); assert.isFalse(iterator.isInitial); // もう一度フレームを移動。 // 3番目のフレームは無いので、一巡して最上位のフレームにフォーカスする。 assert.isTrue(iterator.iterateNext()); // フォーカスは正常に最上位のフレームに移動したか? assert.focus(iterator, content); assert.isFalse(iterator.isInitial); // もう一度フレームを移動。1番目のフレームに戻る。 assert.isTrue(iterator.iterateNext()); // フォーカスは正常に1番目のフレームに移動したか? assert.focus(iterator, content.frames[0]); },
処理が成功したかどうかを確認する手続きを、アサーション(宣言)と呼びます。assert.isTrue、assert.isFalse、assert.equalsなどのアサーション用ヘルパーメソッドは、実行されると渡された値を評価します。実際に渡された値が期待された値と等しければそのまま処理を続行しますが、値が異なっていた場合は「アサーションに失敗した」という内容の例外を発生させてテストの実行が中断されます。これらの例外やメソッド実行時に発生した未知の例外はUxUのインターフェース上に逐次表示され、例外が発生した行のスタックトレースを後で辿ることができるため、どの時点で問題が起こったのか、どこまでは予想通りに正常に動いたのかを詳しく調べてデバッグに役立てられます。
UxUのページのアサーション一覧にないassert.initializedやassert.focusはこのテスト専用に定義したカスタムアサーションです。その実体は、このファイルの冒頭でassertオブジェクトに追加されている新しいメソッドで、以下のように、内部でより単純なアサーションを複数行っています。
assert.focus = function(aIterator, aFrame) { assert.equals(aFrame.location.href, aIterator.view.location.href); assert.equals(aFrame, aIterator.view); assert.equals(aFrame.document, aIterator.document); assert.equals(aFrame.document.body, aIterator.body); var docShell = getDocShellFromFrame(aFrame); assert.docShellEquals(docShell, aIterator.current); assert.isTrue(aIterator.isFindable); }
複数の条件を満たしているかどうかを確認する必要がある場合、そのままテストを記述すると、同じコードがテストケースの中に大量に並んでしまいます。そういった一連の処理はカスタムアサーションとしてまとめておけば、テストケースの内容を綺麗に見やすくできます。
テストの実行は、MozRepl互換のUxUサーバを起動してコンソールから接続して行うか、MozUnit互換のテストランナーを起動して行います。「ツール」メニューから「UnitTest.XUL」→「MozUnitテストランナー」と辿り、テスト実行用のGUIを起動します。
UxUのテスト実行用GUIは、テストケースのファイルのドラッグ&ドロップを受け付けます。実行したいテストのファイル(docShellIterator.test.js)をウィンドウにドラッグ&ドロップすると「作業中のファイル」欄のファイルのパスがドロップされたファイルのものになりますので、後は「実行」ボタンを押すだけで自動テストを実行できます。
テストの現在の実行状況はプログレスメーターで表示されます。アサーションに失敗したり予期しないエラーが起こったりするとプログレスメーターが赤くなりますが、正常にテストが成功すれば緑色のままです。すべてのテストケースの実行結果が緑となることを目指して開発や修正を進めていきましょう。
このように、一連の処理と期待される結果をまとめておき、自動的にテストできるようにしておくと、開発した機能がきちんと動作しているかどうかを人手を煩わさず機械的に確かめられます。一通りのテストケースを作成するにはそれなりの手間と時間を要しますが、一度作成しておけばテストは何度でも簡単に実行できますので、うっかりミスによるエンバグを未然に防ぐ*1ことができます。
Firefox用のアドオンはFirefox自身が持っている機能と連携して動作するように作られることが多く、自動テストを作るのはなかなか大変です。UxUには処理待ち機能をはじめとした多くの便利な機能があり、「このような操作を行った時に、こういう結果になる」といった人間の操作をシミュレートする形の複雑なテストでも、処理の流れを追いやすい形で記述できます。Firefox用アドオンの自動テスト作成にはまさにうってつけと言えるでしょう。
*1 エンバグしてしまってもすぐにそれに気がつくことができる