UxUで始めるFirefoxアドオンの自動テスト - 2008-06-11 - ククログ

ククログ

株式会社クリアコード > ククログ > UxUで始めるFirefoxアドオンの自動テスト

UxUで始めるFirefoxアドオンの自動テスト

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」が正しく機能するかどうかをテストしています。

include

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. エンバグしてしまってもすぐにそれに気がつくことができる