ja ja ja ja ja ja ja ja ja en ja ja ja ja ja ja ja ja ja ja ja en ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja zh ja ja ja ja ja ja ja en ja ja en ja ja en ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja hu ja jbo ja ja ja ja ja ja ja ja ja ja en ja ja en ja ja vi ja sv eu ja en en hu ja ja en en pl ja ja jbo hbs_lat en ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja
UxU(UnitTest.XUL)を利用したFirefoxアドオンのデバッグの例 - 2008-11-17 - ククログ
スクロールが発生しないページ(shortPage.html):ページの内容が短いため、スクロールが発生しません。
スクロールが発生するページ(longPage.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は企業様向けに、一定の支持をいただいています。
クリアコードではカスタマイズなどのサポートをご案内しています。
tDiaryのSubversionバックエンド
tDiaryのデータをHTML化する
Apache Arrow
milter manager
ククログの記事はCC BY-SA 4.0とGFDLのデュアルライセンスで自由に利用できます。
クリアコードはプログラミングが楽しいソフトウェア開発者を1名募集しています。
クリアコードは「クリアコードをいい感じにする人」を1名募集しています。
クリアコードはフリーソフトウェア開発で培った技術力を提供しています。特にMozilla製品(Mozilla FirefoxとMozilla Thunderbird)とRubyとGroonga(全文検索)に関連した開発を得意としています。