この記事は前回からの続きとなります。
AngularJSのE2Eテストといえば、Protractorですね。公式ページにもあるfor AngularJS
の記述がなんとも心強いです。
ProtractorはAngularJSのために開発されているだけあり、テスト実行時に何も考えずともAngularJSと同期してくれるという最強に素晴らしい機能を備えています。
何を同期してくれるのか
AngularJSはモデルの変更を検知するためにScope Life Cycle
という機構を備えています。この機構によりモデルの変更は即座に検知され、同時にビューを最新の状態に保ちます。Protractorはこのライフサイクル中に実行される$digest
ループの完了を検知(同期)してくれるので、ビューの再レンダリングが完了する前にテスト処理が実行されてしまうといった事態を確実に回避してくれます。
また、$timeout
、$resource
, $http
を用いた非同期処理(通信)もしっかりと待機してくれます。
セットアップ
では早速環境構築していきましょう。Protractorのインストールにはnpm
を利用します。
下記コマンドでグローバル領域にProtractorをインストールします。
npm install -g protractor
上記コマンドによりprotractor
, webdriver-manager
の2つのコマンドラインツールがインストールされます。protractor --version
でバージョンを確認することが出来ます。
webdriver-manager
は動作中のSeleniumサーバーのインスタンスを取得するためのヘルパーツールです。
次のコマンドを実行し、動作に必要なバイナリをダウンロードします。
webdriver-manager update
完了後、次のコマンドでSeleniumサーバーを起動させます。
webdriver-manager start
- Protractorはテスト実行時ローカルのブラウザを操作するため、Seleniumサーバーへリクエストを送ります。
-
http://localhost:4444/wd/hub
にアクセスするとSeleniumサーバーの状態を確認することが出来ます。
以上でセットアップは完了です。
テストを書く
Protractorを用いたE2Eテストを実行するためには最低2つのファイルを作成する必要があります。
- 利用するFrameWorkやSeleniumサーバーのアドレスを指定しておくための設定ファイル
- 実際のテストコードを記述したファイル
の2つです。
conf.js
まずは設定ファイルから記述してみます。下記のように記述します。
exports.config = {
// Testing FrameWorkにjasmineを利用
framework: 'jasmine',
// seleniumの待ち受けURLを指定
seleniumAddress: 'http://localhost:4444/wd/hub',
// 動作させるspecファイルを指定
specs: ['todoListSpec.js'],
// テストさせるブラウザを指定
multiCapabilities: [
{ browserName: 'firefox' },
{ browserName: 'chrome' }
]
};
todoListSpec.js
次に実際に走らせるテストコードを記述していきます。
/**
* todoListコンポーネントに関連するE2Eテストグループ
*/
describe('todoListコンポーネントのE2Eテスト', function() {
// タスク入力用のInput要素への参照を取得
var taskInput = element(by.model('$ctrl.data.text'));
// 追加ボタンへの参照を取得
var addButton = element(by.buttonText('追加'));
// タスク表示用リピート要素への参照を取得
var tasks = element.all(by.repeater('item in $ctrl.items'));
beforeEach(function() {
// todoComponentを使用しているページへアクセス(今回で言うとindex.htmlが表示されるURL)
browser.get('http://todo.local.lcl');
});
it('初期タスクとして3個のタスクがレンダリングされているか', function() {
// `by.repeater()`で指定した要素が3個レンダリングされているか
expect(tasks.count()).toEqual(3);
});
it('タスク追加、削除の流れが正常に行えるか', function() {
// タスク入力用Inputに指定の文字列を入力
taskInput.sendKeys('追加タスク');
// 追加ボタンをクリック
addButton.click();
// 追加後、リピート要素が4個になっているか
expect(tasks.count()).toEqual(4);
// 追加したタスクのテキストがInputへ入力したものと一致しているか
expect(tasks.last().$('span').getText()).toEqual('追加タスク');
// タスクを古いものから順に3個削除する
tasks.last().$('button').click();
tasks.last().$('button').click();
tasks.last().$('button').click();
// リピート要素が1個になっているか
expect(tasks.count()).toEqual(1);
});
});
Jasmineの使い方は前回と変わりません。describe()
でテストグループを作成し、it()
で個々のテスト内容を記述していきます。
Protractor特有のオブジェクトとしてelement
, by
, browser
があります。
element
はその名の通りエレメントへの参照を取得するための関数、
by
はエレメントへの参照をどのように解決するかを定義するための機能を多数保有しており、binding()
, model()
, buttonText()
, repeater()
といった関数を提供しています。binding()
はng-bind
、もしくは{{}}
でバインドしている変数名をセレクタとして渡します。model()
へはng-model=""
で指定している変数名、buttonText()
へはボタン要素のテキスト部分、repeater()
へはng-repeat=""
に渡している文字列を指定したり出来ます。
by
は他にも便利な関数がたくさんありますが、詳細は公式リファレンスを参照するのが良いかと思います。
browser.get()
で引数に渡したURLにアクセスしてくれます。前回作成したTODOアプリのテストを行いたいので、そのアプリが閲覧出来るURLを指定しています。
実行
では実行してみます。
実行する際は下記のようにconf.js
を指定して起動します。
protractor conf.js
すると指定したブラウザが立ち上がり一瞬でテストが実行され、完了後ブラウザが終了します。
今回はChromeとFireFoxを指定したので、2つのブラウザが立ち上がりテストが実行されました。サックサクですね。
結果はコマンドを実行したコンソール上に下記のように表示されます。
[launcher] Running 2 instances of WebDriver
..
------------------------------------
[chrome #11] PID: 448
[chrome #11] Specs: /var/www/html/todo/e2eTest/todoListSpec.js
[chrome #11]
[chrome #11] Using the selenium server at http://localhost:4444/wd/hub
[chrome #11] Started
[chrome #11] ..
[chrome #11]
[chrome #11]
[chrome #11] 2 specs, 0 failures
[chrome #11] Finished in 2.283 seconds
[launcher] 1 instance(s) of WebDriver still running
..
------------------------------------
[firefox #01] PID: 443
[firefox #01] Specs: /var/www/html/todo/e2eTest/todoListSpec.js
[firefox #01]
[firefox #01] Using the selenium server at http://localhost:4444/wd/hub
[firefox #01] Started
[firefox #01] ..
[firefox #01]
[firefox #01]
[firefox #01] 2 specs, 0 failures
[firefox #01] Finished in 2.212 seconds
[launcher] 0 instance(s) of WebDriver still running
[launcher] chrome #11 passed
[launcher] firefox #01 passed
どちらのブラウザもpassed
が表示されています。成功です。
おわりに
Protractorを用いたE2Eテストの雰囲気は伝わったでしょうか。E2Eテストは書いていて本当に楽しいですね。テストを動作させた時の「シュババババババッ」感がたまりません(哲学)。
P.S.
テスト書こう。絶対。