E2Eテスト
もし、新しいAngularプロジェクトを始めるのであれば、近い将来現在のE2Eテストのメソッドが置き換わる可能性のある Protractorの使用を検討するのが良いかもしれません。
アプリケーションは成長していく度にサイズが増え複雑さを増すため、 手動になりがちな新しい機能の実装や調整の検証テストは非現実的になり、 バグやレベルダウンの原因になります。
この問題を解決するために、ユーザー操作をシミュレーションするAngularのシナリオランナーが用意されており、 Angularアプリケーションを検証する際に手助けしてくれます。
概要
特定された状態での与えられた確定操作において、アプリケーションがどのように振る舞うかを説明するJavaScriptのシナリオテストを書きます。
シナリオは、command
(コマンド)とexpectation
(期待)から作成される、
1つまたは複数のit
ブロック(アプリケーションでこれらを必要とすると考えられる事)から構成されます。
commandはRunnerにアプリケーションで行う内容を伝え(例:ページの移動やボタンのクリック)、
expectはRunnerに状態についての何らかのアサート(こうであるべきという主張)を伝えます(例:フィールドの値、現在URL等)。
もし、何らかのexpectationが失敗すると、Runnerは"failed"としてそれをマークして次に進みます。
シナリオはまた、成功失敗に関わらず各itブロックの前(または後)で実行される、
beforeEach
とafterEach
を持つかもしれません。
上記の要素に加えて、シナリオはitブロック内でのコードの重複を避けるためヘルパー関数を含めることも可能です。
下記はシンプルなシナリオの例です。
describe('Buzz Client', function() {
it('should filter results', function() {
input('user').enter('jacksparrow');
element(':button').click();
expect(repeater('ul li').count()).toEqual(10);
input('filterText').enter('Bees');
expect(repeater('ul li').count()).toEqual(1);
});
});
input('user')は、 name="user"ではなく、ng-model="user"付きの<input>要素を見つけることに注意してください。
このシナリオは具体的に、"Buzz Client"ユーザーがフィルタリング手続き可能であるという要件を説明しています。 まずng-model="user"付きのinputフィールドへ値が入れられることで開始され、 ページ上の唯一のボタンがクリックされ、その後10項目のリストが存在していることを検証します。 次にng-model='filterText'付きのinputフィールドへ'Bees'と入力し、 リストが1つの項目に絞られることを検証します。
下記のAPIのセクションは、Runnerで利用可能なcommandとexpectationの一覧になります。
API
ソース: https://github.com/angular/angular.js/blob/master/src/ngScenario/dsl.js
マッチャー
マッチャー(Matcher)は、上述したexpect(...)関数と組み合わせて使用され、not()を付けて否定することが可能です。
例: expect(element('h1').text()).not().toEqual('Error')
ソースコード: https://github.com/angular/angular.js/blob/master/src/ngScenario/matchers.js
// 値とオブジェクトは、angular.equals()のルールに従って比較されます。
expect(value).toEqual(value)
// 単純な===を使用した値の比較です。
expect(value).toBe(value)
// 型をチェックすることで値が定義されているか確認します。
expect(value).toBeDefined()
// JavaScript標準のtrue/falseのルールを使用する2つのマッチャーです。
expect(value).toBeTruthy()
expect(value).toBeFalsy()
// 値(value)が指定された正規表現にマッチするか検証します。
// 正規表現は文字列または正規表現オブジェクトのどちらかを渡すことが出来ます。
expect(value).toMatch(expectedRegExp)
// null値であるかを===を使用して確認します。
expect(value).toBeNull()
// Array.indexOf(...)が使用され、要素が配列内に含まれているか否かを確認します。
expect(value).toContain(expected)
// < と >を使用して数値を比較します。
expect(value).toBeLessThan(expected)
expect(value).toBeGreaterThan(expected)
例
他の例も確認したいのであれば、angular-seedプロジェクトを参照してください。
element(...).query(fn)を使用した条件付きのアクション
AngularシナリオのE2Eテストは、キューのアクションによって高度な非同期と多くの複雑さを隠蔽し、
expectationは見込み処理を扱うことが出来ます。
時折、条件付きのアサーション、または要素選択が必要になる場合があります。
通常はこれの使用は避けるべきなのですが(不安定なテストになりがちであるため)、
条件の振る舞いをelement(...).query(fn)
を使用して追加することが可能です。
下記のコードは、この関数がアプリケーションのWEBインターフェースを使用して追加された、
入力(入力は何らかのドメインオブジェクト)を削除するのに使用する方法についての一覧を示しています。
アプリケーションが下記の2つのビューから構成されているとします。
- 概観ビューはテーブルに全ての追加された入力の一覧を表示します。
- 詳細ビューは入力の詳細を表示し、削除ボタンが含まれています。 削除ボタンがクリックされると、ユーザーは概観ページへリダイレクトされて戻ります。
beforeEach(function () {
var deleteEntry = function () {
browser().navigateTo('/entries');
// <tbody>要素を入力がなにも無いかもしれないケースを想定して
// 選択する必要があります。(そのため行は無し)
// セレクタがマッチ結果を得られなかった場合、テストは失敗がマークされます。
element('table tbody').query(function (tbody, done) {
// ngScenarioはjQuery liteでラップされた要素を与えてくれます。
// `children()`関数を呼び出し、テーブルボディの行を取得します。
var children = tbody.children();
if (children.length > 0) {
// もし少なくとも1つテーブルへ入力があれば、
// リンクをクリックし、入力の詳細ビューへ。
element('table tbody a').click();
// そしてroute変更後、削除ボタンをクリックします。
element('.btn-danger').click();
}
// もし、複数の入力がテーブル上にあれば、
// 削除アクションを別にキュー登録します。
if (children.length > 1) {
deleteEntry();
}
// ngScenarioがテストの実行を続けられるように、
// `done()`を呼び出すことを忘れないで下さい。
done();
});
};
// 入力の削除開始
deleteEntry();
});
何が起こっているのを理解するために、ngScenarioが即座に実行されないことを重点に置く必要があったためキュー登録を行いました。 (ngScenario中、見込みアクションの追加について話しました) もし、テーブルに1つの入力しか無い場合、下記の見込みアクションはキュー登録されます。
// 入力1 削除
browser().navigateTo('/entries');
element('table tbody').query(function (tbody, done) { ... });
element('table tbody a');
element('.btn-danger').click();
2つ入力があれば、ngScenarioは下記のキューを動作させなければいけません。
// 入力1 削除
browser().navigateTo('/entries');
element('table tbody').query(function (tbody, done) { ... });
element('table tbody a');
element('.btn-danger').click();
// 入力2 削除
// "再起の深さ"を表すためのインデント
browser().navigateTo('/entries');
element('table tbody').query(function (tbody, done) { ... });
element('table tbody a');
element('.btn-danger').click();
注意事項
ngScenarioは、angular.bootstrapを使用した手動によるアプリケーションの起動では動作しません。 ng-appディレクティブを使用しなければいけません。
© 2017 Google
Licensed under the Creative Commons Attribution License 3.0.
このページは、ページトップのリンク先のAngularJS公式ドキュメント内のページを翻訳した内容を基に構成されています。 下記の項目を確認し、必要に応じて公式のドキュメントをご確認ください。 もし、誤訳などの間違いを見つけましたら、 @tomofまで教えていただければ幸いです。
- AngularJSの更新頻度が高いため、元のコンテンツと比べてドキュメントの情報が古くなっている可能性があります。
- "訳注:"などの断わりを入れた上で、日本人向けの情報やより分かり易くするための追記を行っている事があります。