- UxUの紹介ページに戻る
- アサーション一覧
- UxUによるGreasemonkeyスクリプトのテスト
- テストケース内で利用可能なヘルパーメソッド
- モックを使ったテスト
- UxUをリモート操作する
- UxUのコマンドラインオプション
テストケースの書き方
UxUは、特定のルールに則って記述されたJavaScriptファイルをテストケースとして実行します。
テストケースとしての内容を含むスクリプトのファイル名は、「<テスト名>.test.js」という命名規則に則って付けることをお勧めします。UxUは特定のフォルダ内に含まれているテストを一括実行する機能を含んでいますが、この命名規則に則っておくと、UxUはテストケースとして実行可能なファイルだけを適切に認識することができます。
テストケースの実行コンテキストでは、UxUによって定義されたヘルパーメソッドが利用できます。詳細はテストケース内で利用可能なヘルパーメソッドをご覧下さい。
実際にUxU用に書かれたテストケースの例として、UxU自身のテストがソースツリー内にあります。UxUのSubversionリポジトリ全体をチェックアウトした上で tests/uxu/ 内にあるテストを実行してみてください。
単純なテスト
UxUでは、以下の要領で1ファイルにつき1つのテストケースを定義します。
var description = 'このテストケースの説明';
function setUp() {
// 初期化処理
// (インスタンスの生成など、各テストを実行する前に必ず実行する内容)
}
function tearDown() {
// 終了処理
// (インスタンスの破棄など、各テストを実行する前に必ず実行する内容)
}
function startUp()
{
// 前初期化処理
// (クラス定義の読み込みなど、テストケース全体の最初に実行する処理)
}
function shutDown()
{
// 後終了処理
// (テストケース全体の最後に実行する処理)
}
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; |
startUp |
前初期化処理 | function.isStartUp = true; |
shutDown |
後終了処理 | function.isShutDown = true; |
test~ |
個々のテスト関数 | function.isTest = true; |
これらのテスト用関数として認識された関数は、以下の順番で実行されます。
- グローバルな名前空間に直接書かれたコード
- startUp(前初期化処理)(あれば)
- setUp(初期化処理)(あれば)
- 1個目のテスト関数
- tearDown(終了処理)(あれば)
- setUp(あれば)
- 2個目のテスト関数
- tearDown(あれば)
- …中略…
- setUp(あれば)
- N個目のテスト関数
- tearDown(あれば)
- shutDown(後終了処理)(あれば)
ただし、個々のテスト関数の実行順序は保証されません。個々のテストはテストケース内でテスト関数を定義した順番と同じ順番で実行されるかもしれませんし、バラバラの順番で実行されるかもしれません。
テストケースそのものの名前、説明など補足的な情報を提供する場合は、グローバルな名前空間においてdescriptionという名前の変数に文字列で説明文を格納してください。テスト実行時などに自動的に利用されます。
※UxU 0.7.xまでは、startUp/shutDownはwarmUp/coolDownという名前でした。後方互換性のため、UxU 0.8.0以降でwarmUp/coolDownという名前を使用した場合はstartUp/shutDownとして扱われます。
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式に渡す値の内容によって変化します。詳細は以下の表の通りです。
| 渡す値 | 説明と例 |
|---|---|
| 数値 | 渡された数値をミリ秒(1000ミリ秒=1秒)単位のウェイト指定として解釈し、指定時間後に処理を再開します。
|
valueプロパティを持つオブジェクト |
渡されたオブジェクトの
|
| 関数(※) | 渡された関数を実行した返り値が偽(false)である間は処理を一時停止し、返り値が真(true)になった時点で処理を再開します。いつまで待っても処理が再開されない(返り値が真にならない)場合は、30秒でタイムアウトします。
|
| ジェネレータイテレータ(※) | 渡されたジェネレータイテレータについて、自動的にイテレーションを行い、その間処理を一時停止します。イテレーションが終了した段階(
|
| ジェネレータ関数(※) | 渡されたジェネレータ関数の返り値として取得されるジェネレータイテレータを使用し、yield式にジェネレータイテレータを渡した場合と同様に処理します。
|
| JSDeferredのDeferredオブジェクト | 処理を一時停止し、渡されたDeferredオブジェクトのDeferredチェインの終端に達するまで待ってから処理を再開します。既にDeferredチェインが終端に達している場合、すぐに処理を再開します。Deferredチェインが途中でキャンセルされた場合や、いつまで待っても処理がチェインの終端に達しない場合は、30秒でタイムアウトします。
処理待ちの復帰条件として使えるDeferredオブジェクトは、UxUの名前空間で定義済みのDeferredクラスから生成された物のみに限られます。テスト対象のコードが独自にJSDeferredを読み込んでいる場合、そのDeferredオブジェクトは復帰条件としては利用できません。テストの際は、Deferredクラスへの参照をUxUの名前空間のDeferredクラスに差し替えるなどの方法を方法を採る必要があります。 |
| DOMイベント指定とDOMイベントターゲットのペア | 指定されたDOMイベントが対になるDOMイベントターゲットに対して発火するまで待って、処理を次に進めます。複数のDOMイベントとイベントターゲットの対を指定した場合、いずれか1つのイベントが発火した時点で処理を次に進めます。この時、発火したイベントのオブジェクトを
イベント名の文字列の代わりにハッシュを渡すと、より細かい条件で捕捉対象のイベントを指定することができます。
|
| 上記以外 | 渡された値が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 0.8.x以降では、ローカルHTTPサーバをモックとして動作させることが可能になりました。場合によっては、ローカルHTTPサーバのモックを使用した方が効率よくテストを記述できることもありますので、モックの説明も併せて参照して下さい。
特定の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の検出ルールには、ワイルドカードとして「*」(0個以上の任意の文字にマッチ)と「?」(任意の1文字にマッチ)を利用できます。
|
| 配列 | 配列の奇数番目の要素を置き換え前URIの検出ルール、偶数番目の要素を置き換え先のURIとして解釈します。置き換え前URIの検出ルールには、ワイルドカードとして「*」(0個以上の任意の文字にマッチ)と「?」(任意の1文字にマッチ)を利用できます。また、置き換え前URIの検出ルールとして正規表現リテラルも利用できます。
|
| 関数 | URIの置き換えルールとして関数を使用する場合、その関数は引数としてnsIURI形式で元のURIを受け取り、文字列として置き換え後のURIを返す必要があります。元のURIと同じURI、空文字、
|
※UxU 0.7.5以前では、mappingではなくredirectという名前の変数を使用する必要がありました。後方互換性のため、UxU 0.7.6以降でもredirectによる指定も有効となっています。
ローカルHTTPサーバをモックとして利用すると、同じURIでのアクセスに対して場合に応じて様々な内容を返すようなHTTPサーバとの連携を前提としたテストをより容易に記述することができます。