株式会社クリアコード > フリーソフトウェア開発 > UxU - UnitTest.XUL > UxU用のテストケースの書き方

UxU用のテストケースの書き方 UxU - UnitTest.XUL

テストケースの書き方

UxUは、特定のルールに則って記述されたJavaScriptファイルをテストケースとして実行します。

テストケースとしての内容を含むスクリプトのファイル名は、「<テスト名>.test.js」という命名規則に則って付けることをお勧めします。UxUは特定のフォルダ内に含まれているテストを一括実行する機能を含んでいますが、この命名規則に則っておくと、UxUはテストケースとして実行可能なファイルだけを適切に認識することができます。

テストケースの実行コンテキストでは、UxUによって定義されたヘルパーメソッドが利用できます。詳細はテストケース内で利用可能なヘルパーメソッドをご覧下さい。

実際にUxU用に書かれたテストケースの例として、UxU自身のテストがソースツリー内にありますUxUのSubversionリポジトリ全体をチェックアウトした上で tests/uxu/ 内にあるテストを実行してみてください。

単純なテスト

UxUでは、以下の要領で1ファイルにつき1つのテストケースを定義します。

var description = 'このテストケースの説明';

function setUp() {
  // 初期化処理
  // (インスタンスの生成など、各テストを実行する前に必ず実行する内容)
}

function tearDown() {
  // 終了処理
  // (インスタンスの破棄など、各テストを実行する前に必ず実行する内容)
}

function warmUp()
{
  // 前初期化処理
  // (クラス定義の読み込みなど、テストケース全体の最初に実行する処理)
}

function coolDown()
{
  // 後終了処理
  // (テストケース全体の最後に実行する処理)
}

testWillSuccess.description = '成功するテストの例';
testWillSuccess.priority    = 'normal';
function testWillSuccess() {
  assert.equals(0, [].length);
  assert.notEquals(10, ''.length);
  assert.isTrue(true);
  assert.isFalse(false);
  assert.isDefined(assert);
  assert.isUndefined(void(0));
  assert.isNull(null);
  assert.raises('TypeError', (function() { null.property = true; }), this);
  assert.matches(/patterns?/, 'pattern');
}

testWillFail.description = '失敗するテストの例';
testWillFail.priority    = 'low';
function testWillFail() {
  assert.isTrue(false);
}

グローバルな名前空間において以下の表にある名前で始まる名前の関数は、テストケースの内容として自動的に認識されます。また、プロパティで明示的に指定を行うことで、これら以外の名前の関数をテスト用関数として認識させることも可能です。

テスト用関数として自動的に認識される関数の名前
関数名(前方一致)テスト関数としての働きプロパティでの指定
setUp 初期化処理 function.isSetUp = true;
tearDown 終了処理 function.isTearDown = true;
warmUp 前初期化処理 function.isWarmUp = true;
coolDown 後終了処理 function.isCoolDown = true;
test 個々のテスト関数 function.isTest = true;

これらのテスト用関数として認識された関数は、以下の順番で実行されます。

  1. グローバルな名前空間に直接書かれたコード
  2. warmUp(前初期化処理)(あれば)
  3. setUp(初期化処理)(あれば)
  4. 1個目のテスト関数
  5. tearDown(終了処理)(あれば)
  6. setUp(あれば)
  7. 2個目のテスト関数
  8. tearDown(あれば)
  9. …中略…
  10. setUp(あれば)
  11. N個目のテスト関数
  12. tearDown(あれば)
  13. coolDown(後終了処理)(あれば)

ただし、個々のテスト関数の実行順序は保証されません。個々のテストはテストケース内でテスト関数を定義した順番と同じ順番で実行されるかもしれませんし、バラバラの順番で実行されるかもしれません。

テストケースそのものの名前、説明など補足的な情報を提供する場合は、グローバルな名前空間においてdescriptionという名前の変数に文字列で説明文を格納してください。テスト実行時などに自動的に利用されます。

MozUnit(MozLab)互換の記法

UxUはMozUnit(MozLab)用の記法で書かれたテストケースも利用できます。

UxUとMozUnitの相違点として、UxUではrunStrategyオプションは廃止されています。これは、後述する処理待ち機能によって、同期テストと非同期テストを定義時点で区別する必要がないためです。(ただし後方互換性のため、setUpが引数を受け取るようになっている場合は、MozUnit用の非同期テストと同様に振る舞うようになります。)

処理待ちを使ったテスト

UxU用のテストケースでは、utils.wait()またはyield式によって、テストの任意の位置で処理待ちを行うことができます。(utils.wait()はFirefox 3およびThunderbird 3以降でのみ利用できます。Firefox 3以降またはThunderbird 3以降専用のテストではutils.wait()を使い、Firefox 2またはThunderbird 2もサポート対象に含める場合はyieldを使うとよいでしょう。)

テストケースや初期化処理の中にutils.wait()またはyield式を書くと、UxUはその行で処理を一時停止し、一定の条件が満たされた後に次の行から処理を再開します。処理を再開する条件はutils.wait()またはyield式に渡す値の内容によって変化します。詳細は以下の表の通りです。

utils.wait()およびyeild式に渡す値の内容と処理の再開条件の一覧
渡す値説明と例
数値

渡された数値をミリ秒(1000ミリ秒=1秒)単位のウェイト指定として解釈し、指定時間後に処理を再開します。

var beforeTime = (new Date()).getTime();
utils.wait(1000); // 1秒待ってから処理を再開
// yield 1000; // 1秒待ってから処理を再開
var afterTime = (new Date()).getTime();
assert.isTrue((afterTime - beforeTime) > 500);
assert.isTrue((afterTime - beforeTime) < 1500);
valueプロパティを持つオブジェクト

渡されたオブジェクトのvalueプロパティの値が偽(false)である間は処理を一時停止し、値が真(true)になった時点で処理を再開します。いつまで待っても処理が再開されない(valueプロパティの値が真にならない)場合は、30秒でタイムアウトします。

var browser;
functionalTest.tests = {
  setUp : function() {
    var loaded = { value : false };
    browser = window.openDialog(
            'chrome://browser/content/browser.xul');
    browser.addEventListener('load', function() {
        loaded.value = true;
    }, false);
    // フラグが立つまで待ってから処理を再開
    utils.wait(loaded);
    // yield loaded;
    browser.gFindBar.openFindBar();
  },
  tearDown : function() {
    browser.close();
  },
  ...
};
関数(※)

渡された関数を実行した返り値が偽(false)である間は処理を一時停止し、返り値が真(true)になった時点で処理を再開します。いつまで待っても処理が再開されない(返り値が真にならない)場合は、30秒でタイムアウトします。

var win = utils.getTestWindw();
var url = win.content.location.href;
// ブラウザで表示しているページが他のページに切り替わる
// (ページを遷移する)まで待ってから処理を再開
var f = function() {
        return url != win.content.location.href
    };
utils.wait(f);
// yield f;
assert.equals(uri_to_be_redirected,
              win.content.location.href);
ジェネレータイテレータ(※)

渡されたジェネレータイテレータについて、自動的にイテレーションを行い、その間処理を一時停止します。イテレーションが終了した段階(StopIteration例外が発生した時点)で、次の行から処理を再開します。

// 一連の処理の完了を待って次に進む
function assert_page_load(aURI) {
  yield utils.loadURI(aURI);
  var win = utils.getTestWindow();
  assert.equals(aURI, win.content.location.href);
}

function assert_window_close() {
  var win = utils.getTestWindow();
  win.close();
  yield 500;
  assert.isTrue(win.closed);
}

utils.wait(assert_page_load('http://www.clear-code.com/'));
// yield assert_page_load('http://www.clear-code.com/');
utils.wait(assert_window_close());
// yield assert_window_close();
ジェネレータ関数(※) 渡されたジェネレータ関数の返り値として取得されるジェネレータイテレータを使用し、yield式にジェネレータイテレータを渡した場合と同様に処理します。
function assert_window_close() {
  ...
}

utils.wait(assert_window_close); // without "()"
// yield assert_window_close;
上記以外 渡された値がvalueプロパティを持たないオブジェクトであるか、負の数値の場合、実行時にエラーとなります。それ以外の場合は、値をNumber()で数値に変換した結果の値が指定されたものと見なします。Number()で数値に変換できなかった場合は、0が指定されたものと見なします。

※関数やジェネレータをyield式に渡す場合の注意事項

以下は、yieldを使う時の注意点です。utils.wait()を使う場合はこの点を気にする必要はありません。

yield式は関数の実行ではないため、スタックを生成しません。よって、yield式に渡された関数やジェネレータ関数、ジェネレータイテレータの中で例外が発生した場合は、スタックトレースを辿ってもyield式が評価された箇所まで到達できず、例外の発生箇所の特定が非常に困難となります。

// 例:マッピングのテスト
function assertRedirect(aOriginalURI, aToBeRedirectedTo) {
  yield utils.loadURI(aOriginalURI);
  assert.equals(aToBeRedirectedTo, browser.currentURI.spec);
}
yield assertRedirect('http://myhost/page1',
                     'http://myhost/page1_redirected');
yield assertRedirect('http://myhost/page2',
                     'http://myhost/page2_redirected');
yield assertRedirect('http://myhost/page3',
                     'http://myhost/page3_redirected');

例えばこのようなテストにおいては、assert.equals()でのアサーションに失敗して例外が発生しても、スタックトレースはassert.equals()がある行までしか辿ることができず、3回あるアサーションのどれが失敗したのかはレポートからは分からないということになります。

このような場合のために、UxUは特殊なヘルパー関数であるDo()を提供します。Do()を使用すると、yield式の評価の際にスタックを強制的に生成させることができ、例外の発生箇所の特定が容易になります。

yield Do(assertRedirect('http://myhost/page1',
                     'http://myhost/page1_redirected'));
yield Do(assertRedirect('http://myhost/page2',
                     'http://myhost/page2_redirected'));
yield Do(assertRedirect('http://myhost/page3',
                     'http://myhost/page3_redirected'));

なお、関数やジェネレータイテレータ以外のオブジェクトを渡した場合、Do()は渡されたオブジェクトをそのまま返しますので、テスト自体はDo()を使わない場合と同様に動作します。Do()を書き忘れるミスを防ぐためにも、テストケース中にyield式を書く際には、渡す値を必ずDo()でラップするようにしておくとよいでしょう。

アサーション数の検証

個々のテスト関数には、そのテストで行われなくてはならないアサーションの回数をassertionsプロパティで明示することができます。

testRoop.assertions = 10;
function testRoop() {
  for (var i = 0, maxi = data.length; i < maxi; i++)
  {
    assert.equals(expected[i], data[i], i);
  }
}

アサーションの回数が明示されたテストにおいて、個々のアサーションはすべて成功したものの、期待された回数のアサーションが実行されなかったという場合、UxUはそのテストを失敗と判断します。作業時に一時的にコメントアウトしたアサーションを元に戻し忘れていたという風に、必要なアサーションが実行されないままテストが成功と判断されてしまうことを防げます。

なお、assertionsプロパティによるアサーション数の検証は、テスト関数内でのアサーションに対して行われます。テストケース全体のsetUp/tearDownや、個々のテスト関数のsetUp/tearDown内に書かれたアサーションは、この検証の対象外となります。

アサーションの回数が明示されていないテストにおいて、1つもアサーションが行われないままテストが完了した場合、UxUはその旨を警告した上で、テスト成功として処理します。

minAssertions, maxAssertions

必要に応じて、指定された数未満のアサーションしか実行されなかった場合にテスト失敗と見なすminAssertionsプロパティと、指定された数より多くのアサーションが実行された場合にテスト失敗と見なすmaxAssertionsプロパティも利用できます。これらのプロパティの用法はassertionsプロパティと同様で、それぞれ個別に指定することも、複数を組み合わせて使用することもできます。

テスト用プロファイルの利用

テストを一定の環境で実行することを強制するために、テストにはテスト実行用のプロファイルを指定することができます。

var profile = '../profiles/test-profile/';

function setUp() {
...

この例のように、プロファイルとして使用するフォルダへのパスをグローバルな名前空間の変数profileに格納しておくと、UxUは自動的にそのプロファイルでFirefoxやThunderbirdを起動してテストを実行します。

プロファイルとして指定したフォルダは、実際には、一時ファイルとして複製された物が利用されます。指定したフォルダそのものには変更は行われません。

var profile = '../profiles/test-profile/';
var application = 'C:\\Program Files\\Mozilla Thunderbird\\thunderbird.exe';

...

また、この例のようにアプリケーションの実行ファイルへのパスまたはファイルURLをグローバルな名前空間の変数applicationに格納しておくと、現在のアプリケーションではなくそのアプリケーションでテストを実行します。(※プロファイルが未指定の場合、applicationによる指定は無視されます)

データ駆動テストの記述

特定の処理に対して様々な種類の引数を渡してテストしたい場合のために、UxUはデータ駆動テストの記述もサポートしています。テスト関数のparametersプロパティに配列またはハッシュの形でパラメータ定義を代入すると、そのテストはデータ駆動テストとなります。

例えば、Webページに対して何らかの処理を行う機能について、複数の異なるWebページにアクセスして結果を検証する場合、以下のようにテストを記述することができます。

function testMyFunction() {
  utils.wait(utils.loadURI('http://...'));
  assert.equals('result1', myFunc(utils.content));

  utils.wait(utils.loadURI('http://...'));
  assert.equals('result2', myFunc(utils.content));

  utils.wait(utils.loadURI('http://...'));
  assert.equals('result3', myFunc(utils.content));

  ...
}

しかし、このように記述すると、検証したいWebページが増えれば増えるほどテストが長くなっていきます。また、仮に途中のいずれかの検証結果が失敗(failure)となった場合や、予期しないエラーが発生した場合などは、テストがそこで中断されてしまうため、残りの項目の検証が行われず、テストを繰り返し実施する際の効率が悪くなります。

このようなケースでは、以下のようにパラメータ定義を記述すると効率よくテストを進められます。

testMyFunction.parameters = [
  { uri: 'http://...', expected: 'result1' },
  { uri: 'http://...', expected: 'result2' },
  { uri: 'http://...', expected: 'result3' },
  ...
];
function testMyFunction(aParameter) {
  utils.wait(utils.loadURI(aParameter.uri));
  assert.equals(aParameter.expected, myFunc(utils.content));
}

テスト関数のparametersプロパティに配列としてパラメータ定義が代入されている場合、UxUは配列の各要素をテスト関数の引数として渡す形でそのテストを繰り返し実施します(テスト関数に個別のsetUpやtearDownが指定されている場合、それらにも同じ引数が渡されます)。このようにしておくと、上記の例であれば「testMyFunction (1)」「testMyFunction (2)」「testMyFunction (3)」という具合に各回がそれぞれ別個のテストとして実行されるため、途中のどれかの回でテストが失敗(failure)した場合でも、すべての検証結果を最後にまとめて見ることができます。

テスト関数のparametersプロパティにハッシュとしてパラメータ定義を代入した場合、UxUはハッシュの各要素をテスト関数の引数に渡します。

testMyFunction.parameters = {
  google: { uri: 'http://...', expected: 'result1' },
  yahoo:  { uri: 'http://...', expected: 'result2' },
  mixi:   { uri: 'http://...', expected: 'result3' },
  ...
};
function testMyFunction(aParameter) {
  utils.wait(utils.loadURI(aParameter.uri));
  assert.equals(aParameter.expected, myFunc(utils.content));
}

この場合、実行される各回のテストの名前にはハッシュのキーが付与されます。例えば、上記の例であれば各回のテスト名は「testMyFunction (google)」「testMyFunction (yahoo)」「testMyFunction (mixi)」となります。テストに失敗(failure)した場合などに表示される名前が分かりやすくなるため、デバッグの手間を軽減できます。

データ駆動テストの記述を支援するため、UxUにはCSV形式のデータファイルから配列型やハッシュ型のパラメータ定義を生成するヘルパーメソッドが含まれています。OpenOffice.org CalcやMicrosoft Excelなどでテストに与えるパラメータの内容を管理できるため、項目数の多いテストも容易にメンテナンスできます。

テストを実行しない場合

テストの優先度によるスキップ

個々のテスト関数にはpriorityプロパティによって優先度を設定できます。以下は、テストに優先度を設定する場合の例です。

testBasic.priority = 'must';
function testBasic() {
  // とても重要な処理のテストは、必ず実施する。
  assert.equals(3, calcService.add(1, 2));
  ...
}

testMinor.priority = 'low';
function testMinor() {
  // あまり使われない機能のテストは、たまに実施する。
  ...
}

testUnderConstruction.priority = 'never';
function testUnderConstruction() {
  // 未実装の機能のテストは、実装を終えるまでは実施しない。
  // (毎回エラーが出ると、他のテストが成功したかどうかが
  //   分からなくなるので)
  ...
}

UxUの初期設定は、繰り返し何度もテストを実行する、テスト駆動の開発スタイルを念頭に置いています。多くの場合、前回成功したテストは次も成功する可能性が高いと考えられます。そこで、UxUは前回成功したテストについては一定の確率で実行をスキップするようになっています。ただし、前回実行時に失敗したテストや新しく追加されたテストは必ず実行されます。これにより、一回一回のテストの実行にかかる時間を短縮して気軽に実行できるようになるため、多くのテストを偏り無く実行することができます。

優先度は以下のいずれかを文字列で指定するか、0から1までの数値で指定します。

テストの優先度の一覧
優先度実行される確率数値での指定
must 100% 1
important 90% 0.9
high 70% 0.7
normal(初期値) 50% 0.5
low 25% 0.25
never 0% 0

なお、proprityという名前のグローバル変数を使用すると、そのテストケース全体の優先度を明示的に設定できます。個々のテストに優先度が明示的に指定されていない場合は、テストケース全体の優先度が適用されます。

var priority = 'must';

testFoo.priority = 'never';
function testFoo() { ... } // このテストは"never"なのでスキップされる

function testBar() { ... } // このテストは"must"として扱われる

対象アプリケーションによるスキップ

個々のテスト関数にはtargetProductプロパティによって対象アプリケーションを指定できます。FirefoxとThunderbirdの両方に対応したアドオンなどのように、それぞれのアプリケーションに固有の機能をテストする場合に、この機能を利用できます。

testForFirefox.targetProduct = 'Firefox';
function testForFirefoxSpecificFeature() { ... }

testMinor.targetProduct = 'Thunderbird';
function testForThunderbirdSpecificFeature() { ... }

function testForAllApplication() { ... }

また、targetProductという名前のグローバル変数を使用すると、そのテストケース全体の対象アプリケーションを明示できます。テストケース内のテストを実行するかどうかの判断は最初の時点で行われるため、個々のテストに対して別の対象アプリケーションを指定していた場合、それらはすべて実行をスキップします。

var targetProduct = 'Thunderbird';

testA.targetProduct = 'Firefox';
function testA() { ... } // このテストはスキップされる

function testB() { ... } // このテストは実行される

なお、targetProductグローバル変数によって対象アプリケーションが明示されており、テスト用プロファイルも同時に指定されている場合は、「テストケース全体をスキップする」ではなく、「テスト対象のアプリケーションを指定プロファイルで自動的に起動してテストを実行する」という動作になります。

その他の条件

上記以外の条件でテストの実行をスキップするかどうかを指定したい場合、それぞれのテストケースのshouldSkipプロパティによって、そのテストをスキップすることを明示的に指定できます。shouldSkipプロパティに関数オブジェクトを指定した場合、テストの実行の直前(setUp()が処理されるより前)にその関数を実行し、その返り値を見てスキップするかどうかを判断します。それ以外の値を指定した場合は、相当する真偽値として評価します。

testA.shouldSkip = true;
function testA() { /* 書きかけなので実行しない */ }

// 2009年2月9日以降は実行しないテスト
testB.shouldSkip = function() {
  return Date.now() > (new Date('2009/2/9')).getTime();
};
function testB() { ... }

テストケース全体の実行をスキップさせたい場合は、グローバル変数shouldSkipを使用してください。shouldSkipグローバル変数によってスキップが明示された場合は、個々のテストでの指定は無視されます。

var shoudSkip = true;

// 以下のテストは常にスキップされる
testA.priority = 'must';
function testA() { ... }
function testB() { ... }

URIのマッピング

※他のアドオンとの競合を防ぐため、この機能は初期状態では無効化されています。利用する場合は、UxUの設定において「テストケースのためのURIマッピング機能を有効にする」を有効にしてください。

特定のURIのリソースを読み込むように設計されたモジュールのテストを容易に行えるよう、UxUは簡易的なマッピング機能を内蔵しています。この機能を使うことによって、例えば、http://myservice.example.com/myapi?userid=012345 というURIへのアクセスに対して、あらかじめ用意しておいたローカルのファイル 012345.xml の内容を返す、といったことができます。

var mapping = {
      '*.google.*/*' : baseURL+'../fixtures/google-pages.html',
      '*.yahoo.*/*'  : baseURL+'../fixtures/yahoo-pages.html',
      'https://addons.mozilla.org/ja/firefox/addon/*'
                     : baseURL+'../fixtures/addons/$1.html'
    };

function setUp() { ... }
function tearDown() { ... }
function test_loadInBackground() {
  myModule.loadURL('https://addons.mozilla.org/ja/firefox/addon/6357');
  utils.wait(function() { return myModule.loaded; });
  assert.equals('summary of the loaded document', myModule.getSummary());
}

この例のように、URIの置き換えルールをグローバルな名前空間の変数mappingに格納しておくと、テストケースの実行中に行われるすべての読み込み処理(フレーム内のリソースの読み込み、XMLHttpRequestによる通信など)において、その置き換えルールが適用されます。

マッピング処理はプロキシによる内容の置換と同等の形で行われます。マッピングが行われた場合には、ドキュメントのURIは置換元のURIのままで、ドキュメントの内容だけがマッピング先のURIで示されるリソースとなります。

URIの置き換えルールの定義方法
形式説明と例
ハッシュ(連想配列、オブジェクトリテラル)

ハッシュのキーを置き換え前URIの検出ルール、対応する値を置き換え先のURIとして解釈します。置き換え前URIの検出ルールには、ワイルドカードとして「*」(0個以上の任意の文字にマッチ)と「?」(任意の1文字にマッチ)を利用できます。

var mapping = {
      'http://release.example.com/'  : baseURL+'../fixtures/home.html',
      'http://release.example.com/*' : 'http://test.example.com/$1',
      'http://www??.example.com/*'   : 'http://test.example.com/server$1$2/$3'
    };
配列

配列の奇数番目の要素を置き換え前URIの検出ルール、偶数番目の要素を置き換え先のURIとして解釈します。置き換え前URIの検出ルールには、ワイルドカードとして「*」(0個以上の任意の文字にマッチ)と「?」(任意の1文字にマッチ)を利用できます。また、置き換え前URIの検出ルールとして正規表現リテラルも利用できます。

var mapping = [
      'http://release.example.com/'      , baseURL+'../fixtures/home.html',
      'http://release.example.com/*'     , 'http://test.example.com/$1',
      /^http://www(\d+).example.com/(.*)', 'http://test.example.com/server$1/$2'
    ];
関数

URIの置き換えルールとして関数を使用する場合、その関数は引数としてnsIURI形式で元のURIを受け取り、文字列として置き換え後のURIを返す必要があります。元のURIと同じURI、空文字、falsenullなどを返すと、マッピングは行われません。

var mapping = function(aOriginalURI) {
      return aOriginalURI.spec.indexOf('google') > -1 ?
               baseURL+'../fixtures/google-pages.html' :
               null ;
    };

※UxU 0.7.5以前では、mappingではなくredirectという名前の変数を使用する必要がありました。後方互換性のため、UxU 0.7.6以降でもredirectによる指定も有効となっています。

アサーション

以下のアサーションを利用できます。

内容の比較

void assert.equals(in Object aExpected, in Object aActual, [in String aMessage]) 別名:assertEqual()

特定の値が期待される場面で使用します。期待通りの任意の値が渡されたかどうかを確認し、第2引数として与えられた値が第1引数として与えられた期待値と異なる場合はAssertionFailed例外を発生させます。値の比較はJavaScriptの==演算子と同じ精度で行われます。プリミティブ値、オブジェクト、DOMノード、配列の比較に対応しています。

assert.equals('expectedValue', object.property);
assert.equals($('node'), object.returnsNode());
// 配列の場合は、値同士の比較となる。
assert.equals([$('node1'), $('node2'), $('node2')],
              object.returnsNodesArray());
// オブジェクトの場合は、各プロパティの値同士の比較となる。
assert.equals({ name : 'foo', value : 'bar' },
              object.getResult());
void assert.strictlyEquals(in Object aExpected, in Object aActual, [in String aMessage]) 別名:assertStrictlyEqual()

assert.equals()について、より厳しい基準で比較を行います。具体的には、値の比較はJavaScriptの===演算子と同じ精度で行われます。

var value = 'expectedValue';
assert.strictlyEquals('expectedValue', value); // success
value = new String('expectedValue');
assert.strictlyEquals('expectedValue', value); // failure
void assert.notEquals(in Object aExpected, in Object aActual, [in String aMessage]) 別名:assertNotEqual()

特定の値が期待されない場面で使用します。任意の値と異なる値が期待通りに渡されたかどうかを確認し、第2引数の値が第1引数の値と等しい場合はAssertionFailed例外を発生させます。値の比較はJavaScriptの!=演算子と同じ精度で行われます。プリミティブ値、オブジェクト、DOMノード、配列の比較に対応しています。

assert.notEquals('true', node.getAttribute('disabled'));
assert.notEquals(object.firstItem, object.lastItem);
void assert.notStrictlyEquals(in Object aExpected, in Object aActual, [in String aMessage]) 別名:assertNotStrictlyEqual()

assert.notEquals()について、より厳しい基準で比較を行います。具体的には、値の比較はJavaScriptの!==演算子と同じ精度で行われます。

var value = '10';
assert.notStrictlyEquals(10, value); // success
assert.notStrictlyEquals(10, parseInt(value)); // failure
void assert.contains(in Object aElementShouldBeContained, in Object aElementsOrRange, [in String aMessage]) 別名:assertContain()

特定の値が含まれていることが期待される場面で使用します。第1引数で渡されたオブジェクトが第2引数で渡されたオブジェクトの中に含まれていない場合は、AssertionFailed例外を発生させます。

第2引数にはnsISelection、DOM Range、DOM Node、配列、または文字列のいずれかを渡すことができます(それ以外の型のオブジェクトはすべて文字列として扱われます)。それぞれの場合の具体的な検証内容は以下の表の通りです。

第2引数第1引数検証の内容
nsISelection DOM Range Rangeが、nsISelectionの示す範囲の中に包含されているかどうか。(※nsISelectionが持つRangeのうち1つでも第1引数のRangeに等しい物があるかどうかではなく、nsISelectionが持つRangeのうち1つでも第1引数のRangeを包含する物があるかどうかを検証します。)
DOM Node NodeがnsISelectionの示す範囲の中に包含されているかどうか。
DOM Range DOM Range 第1引数のRangeが、第2引数のRangeの中に包含されているかどうか。
DOM Node NodeがRangeの中に包含されているかどうか。
DOM Node DOM Range 第1引数のRangeが、第2引数のNodeの子孫に含まれているかどうか。
DOM Node 第1引数のNodeが第2引数のNodeの子孫かどうか。
配列 すべての型のオブジェクト 第1引数のオブジェクトが、第2引数の配列の要素かどうか。
それ以外の組み合わせ 第1引数を文字列化した内容が、第2引数を文字列化した内容の中に含まれているかどうか。(※大文字小文字の違いは区別されます。)
assert.contains('- Firefox', window.document.title);

var terms = ['extracted', 'extended', 'expected'];
assert.contains('expected', terms); // success

assert.contains(doc.body.firstChild,
                doc.body); // success

var range = doc.createRange();
range.selectNode(doc.body);
assert.contains(doc.body, range); // success
assert.contains(doc.body.firstChild, range); // success
assert.contains(doc.documentElement, range); // failure
void assert.notContains(in Object aElementShouldNotBeContained, in Object aElementsOrRange, [in String aMessage]) 別名:assertNotContain()

特定の値が含まれていないことが期待される場面で使用します。assert.contains()を反転した動作をします。

assert.notContains('error', object.getResultsArray());
assert.notContains('out of selection', content.getSelection());
void assert.contained(in Object aElementsOrRangeShouldContain, in Object aElement, [in String aMessage]) 別名:assertContained()

特定の範囲や文字列などの中に含まれる値が期待される場面で使用します。第2引数で渡されたオブジェクトが第1引数で渡されたオブジェクトに含まれていない場合、AssertionFailed例外を発生させます。

第1引数と第2引数の種類によって若干動作が変わります。詳しくはassert.contains()の説明をご覧下さい。

assert.contained('Start - Firefox', myAddon.brandName);

var terms = ['extracted', 'extended', 'expected'];
assert.contained(terms, myAddon.lastTerm);

assert.contained(doc.body,
                 doc.body.firstChild); // success

var range = doc.createRange();
range.selectNode(doc.body);
assert.contained(range, doc.body.firstChild); // success
assert.contained(range, doc.documentElement); // failure
void assert.notContained(in Object aElementsOrRangeShouldNotContain, in Object aElement, [in String aMessage]) 別名:assertNotContaied()

特定の範囲や文字列などの中に含まれない値が期待される場面で使用します。assert.contained()を反転した動作をします。

assert.notContained('error,failure', myAddon.lastStatus);
assert.notContained(content.getSelection(), myAddon.getNextNodeOfSelection());
void assert.isTrue(in aExpression, [in String aMessage]) 別名:assertTrue()

値として真(または空でない文字列、0以外の数値など、真偽値として見たときに真となる値)が期待される場面で使用します。期待に反して偽が渡された場合はAssertionFailed例外を発生させます。

checkbox.setAttribute('checked', true);
assert.isTrue(checkbox.checked);
void assert.isFalse(in aExpression, [in String aMessage]) 別名:assertFalse()

値として偽(または空文字列、数値の0、null、undefinedなど、真偽値として見たときに偽となる値)が期待される場面で使用します。期待に反して真が渡された場合はAssertionFailed例外を発生させます。

checkbox.removeAttribute('checked');
assert.isTrue(checkbox.checked);
void assert.inDelta(in Number aExpected, in Number aActual, in Number aDelta, [in String aMessage]) 別名:assertInDelta()

値として一定の範囲内に収まる数値が期待される場面で使用します。値が指定された範囲の外にある(aActual < aExpected - aDelta または aActual > aExpected + aDelta である)場合はAssertionFailed例外を発生させます。

var array = '0,1,2,3,4,5,6,7,8,9'.split(',');
// 約半分の項目をランダムに抽出する
var extracted = array.filter(function(aItem) {
		return Math.random() > 0.5;
	});
// 誤差として±2までを許容
assert.inDelta(5, extracted.length, 2);
void assert.compare(in Number aExpected, in String aOperator, in Number aActual, [in String aMessage]) 別名:assertCompare()

第1引数と第3引数を第2引数の演算子で比較します。期待に反して結果が偽の場合はAssertionFailed例外を発生させます。比較演算子には ==, ===, !=, !==, <, <=, >, >= のいずれかを指定します。比較演算子以外を渡した場合はエラーになります。

assert.compare(100, '<=', func1());
assert.compare(100, '>', func2());

値の比較の注意事項

assert.equals()assert.notEquals()assert.strictlyEquals()assert.notStrictlyEquals()assert.compare()に以下の型のオブジェクトを渡した場合は、特別なルールに則って比較が行われます。

Date

両者のオブジェクトが保持している時刻を比較します。

Array

配列の長さ、各要素を比較します。

Object型(オブジェクトリテラル、ハッシュ、カスタムクラスのインスタンス)

持っているすべてのプロパティの名前と値を比較します。

型の比較

void assert.isBoolean(in aExpression, [in String aMessage]) 別名:assertBoolean()

値が真偽値であることが期待される場面で使用します。期待に反して真偽型でない値が渡された場合はAssertionFailed例外を発生させます。

assert.isBoolean(checkbox.checked);
assert.isBoolean(checkbox.disabled);
assert.isBoolean(utility.getPref('some.boolean.pref'));
void assert.isNotBoolean(in aExpression, [in String aMessage]) 別名:assertNotBoolean()

値が真偽型でないことが期待される場面で使用します。渡された値が期待に反して真偽値である場合はAssertionFailed例外を発生させます。

assert.isNotBoolean(notInitializedObject.booleanProperty);
void assert.isString(in aExpression, [in String aMessage]) 別名:assertString()

値が文字列であることが期待される場面で使用します。期待に反して文字列型でない値が渡された場合はAssertionFailed例外を発生させます。

assert.isString(input.value);
assert.isString(element.getAttribute('undefined-attribute'));
assert.isString(utility.getPref('some.string.pref'));
void assert.isNotString(in aExpression, [in String aMessage]) 別名:assertNotString()

値が文字列型でないことが期待される場面で使用します。渡された値が期待に反して文字列である場合はAssertionFailed例外を発生させます。

assert.isNotString(notInitializedObject.stringProperty);
void assert.isNumber(in aExpression, [in String aMessage]) 別名:assertNumber()

値が数値であることが期待される場面で使用します。期待に反して数値型でない値が渡された場合はAssertionFailed例外を発生させます。

assert.isNumber(httpClient.errorCode);
assert.isNumber(utility.getPref('some.integer.pref'));
void assert.isNotNumber(in aExpression, [in String aMessage]) 別名:assertNotNumber()

値が数値型でないことが期待される場面で使用します。渡された値が期待に反して数値である場合はAssertionFailed例外を発生させます。

assert.isNotString(notInitializedObject.intProperty);
void assert.isFunction(in aExpression, [in String aMessage]) 別名:assertFuction()

値が関数であることが期待される場面で使用します。期待に反して関数でない値が渡された場合はAssertionFailed例外を発生させます。

assert.isFunction(element.hasAttribute);
assert.isFunction(instance.inheritedMethod);
void assert.isNotFunction(in aExpression, [in String aMessage]) 別名:assertNotFunction()

値が関数でないことが期待される場面で使用します。渡された値が期待に反して関数である場合はAssertionFailed例外を発生させます。

assert.isNotFunction(instanceOfSuperClass.methodOfSubClass);
void assert.isDefined(in aExpression, [in String aMessage]) 別名:assertDefined()

定義済み(数値、文字列、真偽値、nullなど)の値が期待される場面で使用します。渡された値が期待に反して未定義(undefined)である場合はAssertionFailed例外を発生させます。

assert.isDefined(instance.inheritedProperty);
assert.isDefined(Ci.myICustomInterface);
void assert.isUndefined(in aExpression, [in String aMessage]) 別名:assertUndefined()

未定義(undefined)の値が期待される場面で使用します。何らかの値が期待に反して渡された場合はAssertionFailed例外を発生させます。

assert.isUndefined(checkbox.valid);
assert.isUndefined(Ci.myIOldCustomInterface);
void assert.isNull(in aExpression, [in String aMessage]) 別名:assertNull()

nullが期待される場面で使用します。nullでない値(falseやundefinedなども含む)が期待に反して渡された場合はAssertionFailed例外を発生させます。

assert.isNull(element.nodeValue);
assert.isNull(utility.getPref('some.undefined.pref'));
void assert.isNotNull(in aExpression, [in String aMessage]) 別名:assertNotNull()

nullが期待されない場面で使用します。nullが期待に反して渡された場合はAssertionFailed例外を発生させます。

assert.isNotNull(element.getAttribute('undefined-attribute'));
assert.isNotNull(utility.getPref('some.defined.pref'));
void assert.inplementsInterface(in aInterface, in aObject, [in String aMessage]) 別名:assert.inplementInterface(), assertInplementsInterface(), assertInplementInterface()

オブジェクトが特定のXPCOMインターフェースを備えていることが期待される場面で使用します。インターフェースはComponents.interfaces.nsI*のリファレンス、もしくはインターフェース名の文字列("nsI*")で指定します。

第2引数に渡されたオブジェクトが指定のインターフェースを備えていない場合は、AssertionFailed例外を発生させます。

assert.implementsInterface(Ci.nsIDOMWindow, getWindow());
assert.implementsInterface('nsIDOMWindow', getWindow());
assert.implementsInterface(Ci.nsIDOMRange, getWindow()); // this will fail

例外の発生と確認

Object assert.raises(in Exception aExpected, in Function aTestTask, in Object aScope, [in String aMessage]) 別名:assertRaise()

特定の例外の発生が期待される場面で使用します。第2引数として渡された関数を、第3引数の値をスコープとして実行し、第1引数で指定された物と同じ例外が期待に反して発生しなかった場合はAssertionFailed例外を発生させます。

loader.INVALID_URI_ERROR = new Error('invalid uri');
loader.open = function(aURI) {
  if (aURI) this.uri = aURI;
  if (!/^https?:/.test(this.uri))
    throw this.INVALID_URI_ERROR;
  window.open(this.uri);
};
loader.uri = 'mailto:test@example.com';

assert.raises(
  loader.INVALID_URI_ERROR,
  loader.open,
  loader
);
assert.raises(
  loader.INVALID_URI_ERROR,
  function() {
    loader.open('ftp://ftp.example.com');
  },
  {}
);

また、このアサーションは返り値として処理待ち用のオブジェクトを返します。実行したい処理が処理待ちを伴う物である場合に利用できます。

utils.wait(assert.raises(
    loader.INVALID_URI_ERROR,
    function() {
      loader.open('http://www.example.com');
      utils.wait(500);
      loader.open('ftp://ftp.example.com');
    },
    {}
  ));
Object assert.notRaises(in Exception aExpected, in Function aTestTask, in Object aScope, [in String aMessage]) 別名:assertNotRaise()

特定の例外の発生が期待されない場面で使用します。第2引数として渡された関数を、第3引数の値をスコープとして実行し、第1引数で指定された物と同じ例外が期待に反して発生した場合はAssertionFailed例外を発生させます。

loader.INVALID_URI_ERROR = new Error('invalid uri');
loader.open = function(aURI) {
  if (aURI) this.uri = aURI;
  if (!/^https?:/.test(this.uri))
    throw this.INVALID_URI_ERROR;
  window.open(this.uri);
};
loader.uri = 'http://www.example.com';

assert.notRaises(
  loader.INVALID_URI_ERROR,
  loader.open,
  loader
);
assert.notRaises(
  loader.INVALID_URI_ERROR,
  function() {
    loader.open('https://secure.example.com');
  },
  {}
);

また、このアサーションは返り値として処理待ち用のオブジェクトを返します。実行したい処理が処理待ちを伴う物である場合に利用できます。

utils.wait(assert.notRaises(
    loader.INVALID_URI_ERROR,
    function() {
      loader.open('http://www1.example.com');
      utils.wait(500);
      loader.open('http://www2.example.com');
    },
    {}
  ));

所要時間の確認

Object assert.finishesWithin(in Number aExpectedTime, in Function aTestTask, in Object aScope, [in String aMessage]) 別名:assert.finishWithin(), assertFinishWithin()

一定時間内に処理が完了することが期待される場面で使用します。第2引数として渡された関数を、第3引数の値をスコープとして実行し、処理を完了するまでに第1引数で指定された時間(単位:ミリ秒)以上の時間がかかった場合はAssertionFailed例外を発生させます。

function functionContainsLongLoop() {
  for (var i = 0, maxi = 5000; i < maxi; i++) {
    process(data[i]);
  }
}
assert.finishesWithin(
  5 * 1000, // 5sec.
  functionContainsLongLoop,
  {}
);

また、このアサーションは返り値として処理待ち用のオブジェクトを返します。実行したい処理が処理待ちを伴う場合に利用できます。

var manager = {
    tasks : [],
    func  : loadAndParse,
    timer : null,
    start : function() {
      this.timer = window.setInterval(function(aSelf) {
          if (!aSelf.tasks.length) {
            window.clearInterval(aSelf.timer);
            return;
          }
          aSelf.func(aSelf.tasks.shift());
        }, 10, this);
    }
  };
manager.tasks.push('http://www.example.com/');
manager.tasks.push('http://www.example.jp/');
manager.tasks.push('http://www.example.net/');

utils.wait(assert.finishesWithin(
    10 * 1000, // 10sec.
    function() {
      manager.start();
      while (manager.tasks.length) {
        utils.wait(100);
      }
    },
    {}
  ));

アサーション回数の確認

Object assert.assertionsCountEquals(in Number aExpectedCount, in Function aTestTask, in Object aScope, [in String aMessage]) 別名:assert.assertionsCountEqual(), assertAssertionsCountEqual()

assertionsプロパティによるアサーション回数の明示の局所版です。

テスト仕様やデータ数により、特定の回数のアサーションが行われることが期待される場面で使用します。第2引数として渡された関数を、第3引数の値をスコープとして実行します。その上で、第1引数で指定された回数のアサーションが実行されなかった場合はAssertionFailed例外を発生させます。

assert.assertionsCountEquals(
  2,
  function() {
    assert.isNotNull(value);
    assert.isFunction(value);
  },
  {}
);

また、このアサーションは返り値として処理待ち用のオブジェクトを返します。実行したい処理が処理待ちを伴う場合に利用できます。

utils.wait(assert.assertionsCountEquals(
    10,
    function() {
      assert.isNotNull(value);
      utils.wait(500);
      assert.isFunction(value);
    },
    {}
  ));
Object assert.assertionsMinCount(in Number aExpectedMinCount, in Function aTestTask, in Object aScope, [in String aMessage]) 別名:assert.assertionsMinCount(), assertAssertionsMinCount()

minAssertionsプロパティによるアサーション回数の明示の局所版です。

テスト仕様やデータ数により、特定の回数以上のアサーションが行われることが期待される場面で使用します。第2引数として渡された関数を、第3引数の値をスコープとして実行します。その上で、第1引数で指定された回数未満のアサーションしか実行されなかった場合はAssertionFailed例外を発生させます。

assert.assertionsMinCount(
  2,
  function() {
    myCustomAssertion(value); // includes 2 or more assertions
  },
  {}
);

また、このアサーションは返り値として処理待ち用のオブジェクトを返します。実行したい処理が処理待ちを伴う場合に利用できます。

utils.wait(assert.assertionsCountEquals(
    10,
    function() {
      utils.wait(myCustomAssertion(value));
    },
    {}
  ));
Object assert.assertionsMaxCount(in Number aExpectedMaxCount, in Function aTestTask, in Object aScope, [in String aMessage]) 別名:assert.assertionsMaxCount(), assertAssertionsMaxCount()

maxAssertionsプロパティによるアサーション回数の明示の局所版です。

テスト仕様やデータ数により、特定の回数以下のアサーションが行われることが期待される場面で使用します。第2引数として渡された関数を、第3引数の値をスコープとして実行します。その上で、第1引数で指定された回数より多くアサーションが実行された場合はAssertionFailed例外を発生させます。

assert.assertionsMinCount(
  2,
  function() {
    myCustomAssertion(value); // includes 2 or less assertions
  },
  {}
);

また、このアサーションは返り値として処理待ち用のオブジェクトを返します。実行したい処理が処理待ちを伴う場合に利用できます。

utils.wait(assert.assertionsCountEquals(
    10,
    function() {
      utils.wait(myCustomAssertion(value));
    },
    {}
  ));

正規表現によるマッチング

void assert.matches(in RegExp aExpected, in String aActual, [in String aMessage]) 別名:assertMatch()

特定のパターンの文字列が期待される場面で使用します。第2引数の値が第1引数の正規表現に期待に反してマッチしなかった場合はAssertionFailed例外を発生させます。

assert.matches(/^(success|skip)$/i, module.result);
assert.matches(/https?:\/\//, module.message);
void assert.notMatches(in RegExp aUnexpected, in String aActual, [in String aMessage]) 別名:assertNotMatch()

特定のパターンでない文字列が期待される場面で使用します。第2引数の値が第1引数の正規表現に期待に反してマッチした場合はAssertionFailed例外を発生させます。

assert.notMatches(/^(failure|error)$/i, module.result);
assert.notMatches(/(ftp|nntp):/, module.message);
void assert.pattern(in String aExpected, in RegExp aActual, [in String aMessage]) 別名:assertPattern()

特定のパターンの正規表現が期待される場面で使用します。第2引数の値(正規表現)が第1引数として与えられた文字列に期待に反してマッチしない場合はAssertionFailed例外を発生させます。

var regexp = module.mailAddressRegExp;
assert.pattern('test@example.com', regexp);
assert.pattern('My Name <test@example.com>', regexp);
void assert.notPattern(in String aUnexpected, in RegExp aActual, [in String aMessage]) 別名:assertNotPattern()

特定のパターンの正規表現が期待されない場面で使用します。第2引数の値(正規表現)がが第1引数として与えられた文字列に期待に反してマッチする場合はAssertionFailed例外を発生させます。

var regexp = module.mailAddressRegExp;
assert.notPattern('foobar@', regexp);
assert.notPattern('My Name (test at example.com)', regexp);

アサーションのその他の機能

すべてのアサーションは、追加の引数として例外発生時に表示するメッセージを指定できます。スタックトレース以外で例外が発生した時の詳しい状況を知る必要がある場合などに利用できます。

また、以下のようにしてカスタムアサーションを定義することもできます。

assert.isOK = function(aActualValue) {
  var expected = 'OK';
  assert.equals(expected, aActualValue);
}