はじめに
株式会社サイシードのインターンシップにて利用しているテストツールについてまとめました。
Protractor,Cucumberjs,ChaiによるAngularjsアプリケーションのテストについて紹介します。
・Protractor( http://www.protractortest.org/ ):
アプリケーションのテストを行う場合はProtractorを使いましょう。
ビューの再レンダリングが完了する前にテスト処理が実行されてしまうといった事態を回避してくれます。
・Cucumberjs( https://github.com/cucumber/cucumber-js/ ):
自然言語に近い形で誰にでもわかりやすいテストコードを書くことができます。
featureとstepDefinitionがあり、featureにはテストのステップを書き、stepDefinitionにはそれぞれのステップに対する実際のテスト処理を定義します。
・Chai( http://chaijs.com/ ):
アサーション(テストの成功と失敗を判定する)のために利用します。
導入環境
・macOS Sierra(10.12.6)
・Java(1.8.0_151)
・Node.js(v6.11.1)
・npm(3.10.10)
準備
インストール手順など利用前の準備について説明します。
実際の使用方法についてのみ知りたい場合は「使用方法について」からお読みください。
1. インストール
Protractor、Cucumberjs、Chaiをインストールします。
また、Javaがインストールされている必要があります。
1.1 Protractorのインストール
$ npm install --save-dev protractor
$ webdriver-manager update
$ webdriver-manager start
1.2 Cucumberjsのインストール
$ npm install --save-dev cucumber
1.3 ProtractorでCucumberjsを利用できるようにする
$ npm install --save-dev protractor-cucumber-framework
1.4 Chaiのインストール
$ npm install --save-dev chai chai-as-promised
1.5 coのインストール(async/awaitを使えるのであれば不要)
ProtractorのほとんどのAPIがPromiseを返すので、Promiseを扱いやすくするためにcoをインストールする。
Nodejsのバージョンが7.6.0以上の場合は、async/awaitがデフォルトで使えるのでそちらを利用すると良いと思います。
$ npm install --save co
2. 設定ファイルの編集
Protractorの設定ファイルを以下のように編集します。
※ Cucumberjsのタグについて
タグは、「@タグ名」の形で記入する。
「~」を「@」の前に置くとそのタグ以外を指定したことになる。
exports.config = {
// seleniumの待ち受けURLを指定
seleniumAddress: 'http://localhost:4444/wd/hub',
framework: 'custom',
// テストさせるブラウザを指定
multiCapabilities: [
{ browserName: 'chrome' }
],
frameworkPath: require.resolve('protractor-cucumber-framework'),
// 実行するテストファイルを選択する
specs: [
'features/versionUpTest.feature'
],
// cucumberjsの設定
cucumberOpts: {
// 利用するstepDefinitionを指定する。
require: [
'features/step_definitions/stepDefinitions.js'
],
// 実行するfeatureまたはscenarioのタグを指定する。
tags: '~@skip',
strict: true,
format: ['progress'],
profile: false,
'no-source': true,
},
onPrepare: function () {
browser.manage().window().maximize(); // maximize the browser before executing the feature files
}
};
使用方法について
テストコードの書き方の例からテストコードの実行方法、Protractor、Cucumberjs、Chai、それぞれのよく利用するAPIなどを説明します。
1. 書き方の例
Cucumberjsでは、featureと呼ばれるファイルに自然言語に近い形のテストコードを書き、一ステップごとのテスト内容の定義をStepDefinition呼ばれる別のファイルに書きます。
Protractor及びChaiはStepDefinitionで利用します。
1.1 Feature
# language: ja
フィーチャ: テスト
シナリオ: ログイン
前提 "/"へページ遷移する
かつ "input[type='email']"に"emailを入力する
かつ "input[type='password']"に"password"を入力する
かつ ".login-button"をクリックする
ならば "/responselist"になる
@skip
シナリオ: メッセージに返信する
前提 新着メッセージがある
もし "/responselist"へページ遷移する
もし ".response__textarea"に"メッセージ"を入力する
かつ ".response__button"をクリックする
ならば メッセージが送信される
1.2 StepDefinition
//'use strict';
const co = require('co');
const EC = protractor.ExpectedConditions;
const chai = require('chai');
const chaiAsPromised = require('chai-as-promised');
chai.use(chaiAsPromised);
const expect = chai.expect;
defineSupportCode(function({Given, Then, When, setDefaultTimeout}) {
setDefaultTimeout(30 * 1000);
Given(/^"(.*)"へページ遷移する$/, function(url, done){
console.log(`login finished ${url}`);
co(function*() {
browser.ignoreSynchronization = true;
browser.get(`${baseUrl}${url}`);
browser.ignoreSynchronization = false;
done();
});
});
When(/^"(.*)"に"(.*)"を入力する$/, function(cssName, text, done){
co(function*() {
yield element(by.css(cssName)).sendKeys(text);
done();
});
});
When(/^"(.*)"をクリックする$/, function(cssName, done){
co(function*() {
yield element(by.css(cssName)).click();
done();
});
});
Then(/^"(.*)"が存在する$/, function(cssName, done){
co(function*() {
expect(element.all(by.css(cssName))).to.exist;
done();
});
});
});
2. テストの実行方法
一番はじめに以下を実行します。
$ webdriver-manager update
上のコマンドを一度実行してからは、それ以降は以下の2つのコマンドを別々のターミナルで実行すればテストを実行することができる。
webdriverのサーバーを立ち上げる。
$ webdriver-manager start
実行したいテストの設定ファイルを指定して実行する。
$ protractor conf.js
2. Protracorの使い方
よく利用するAPIの説明と私がつまづいた部分についての補足をします。
2.1 よく利用するAPI
ここで説明しているAPI以外も知りたい場合は次のURLをご覧ください。
http://www.protractortest.org/#/api
[ページに対する処理]
(1) Angular以外の処理を行う場合はProtractorの同期機能を切っておく
browser.ignoreSynchronization = true;
(2) ”path”のページに移動する
browser.get(path);
[要素を指定する]
(3) ”cssName”で指定した要素を取得する
element(by.css(cssName));
(4) ”cssName”で指定した要素のうち”text”を含むものを取得する
element(by.cssContainingText(cssName, text));
[指定した要素に対して処理を行う]
(5) 指定した要素に"text"を入力する
sendKeys(text)
//返り値はPromise。
element(by.css(cssName)).sendKeys(text);
(6) 指定した要素をクリックする
click()
//返り値はPromise。
element(by.css(cssName)).click();
(7) 指定した要素の数を数える
count()
//返り値はPromise。
element.all(by.css(cssName)).count();
(8) 指定した要素のテキストを取得する
getText()
//返り値はPromise。
element(by.css(cssName)).getText();
(9) 指定した要素の"attributeName"属性を取得する
getAttribute(attributeName)
//返り値はPromise。
element(by.css(cssName)).getAttribute(attributeName);
(10) 指定した要素が存在する場合はtrueを返す
isPresent()
//返り値はPromise。
element(by.css(cssName)).isPresent();
[要素やページなどが変化したかどうかを判定する]
(11) ”path”のページになるまで待つ
protractor.ExpectedConditions.urlIs(path)
//返り値はPromise。
browser.wait(protractor.ExpectedConditions.urlIs(path), waitTimeDefault);
2.2 補足
(1) 待ってから処理を行いたい時は、thenのブロック内に書く
ProtractorのAPIの返り値はPromiseであることが多いため。
coを利用して以下のように書くとわかりやすい。
co(function*() {
yield browser.wait(EC.urlIs(path), waitTimeDefault);
});
(2) 値を取得する処理の返り値がPromiseのとき、具体的な値を取得したい時は、thenで取得する
co(function*() {
const count = yield element.all(by.css(cssName)).count();
});
(3) Protractorでは各WebDriverの処理の前でwaitForAngular()が呼ばれる
waitForAngular()が自動的に呼ばれることで、基本的に手動で待ち処理を書く必要がなくなる。
Angularがレンダリングを終えるまで待ち、未処理のhttpまたはhttpsリクエストがないことを確認する。
(4) 要素を指定するときの省略
element.all(by.css(cssName)).first();
element.all(by.css(cssName)).get(0);
は全て同じで、 以下のように省略することもできる。
element(by.css(cssName));
(5) 要素を指定するときのメソッドチェーン
指定した要素の下にある要素を順々に指定していくことができる。
element(by.css(cssName)).element(by.css(cssName))
ある要素の下にあるものを全て指定したい時は以下のようにする。
(一つに絞った要素).all(by.css(cssName));
element.all(by.css(cssName)).get(0).all(by.css(cssName))
3. Cucumberjsの使い方
Featureのテスト内容を定義するStep DefinitionsとFeatureあるいはScenarioの前後に処理を挟むことができるHooksについて説明します。
詳しく知りたい場合は以下をご覧ください。
https://github.com/cucumber/cucumber-js/blob/master/docs/support_files/api_reference.md
また、私がつまづいた部分の補足をします。
3.1 Step Definitions
defineStep()でfeatureのステップを定義する。
エイリアスとして、Given, When, Thenがある。
さらに、featureでは、And、Butも利用することができる。
第一引数には文字列または正規表現を渡す。
第二引数のfunctionには正規表現で任意の文字列を指定した部分に該当するものが引数として渡される。
When(/^"(.*)"の内"(.*)"が含まれるものをクリックする$/, function(cssName, text, done){
co(function*() {
yield element(by.cssContainingText(cssName, text)).click();
done();
});
});
3.2 Hooks
各Senarioの前後、最初のSenarioが始まる前と最後のSenarioが終わった後で実行したい処理を記述することができる。
どのタグを持ったFeatureまたはSenarioに対して行うか指定することもできる。
var {After, Before} = require('cucumber');
Before(function () {
// This hook will be executed before all scenarios
});
Before({tags: "@foo"}, function () {
// This hook will be executed before scenarios tagged with @foo
});
(1) Before
各シナリオの前に実行したい処理を記述する。
(2) After
各シナリオの後に実行したい処理を記述する。
(3) BeforeAll
最初のシナリオの前に実行したい処理を記述する。
(4) AfterAll
最後のシナリオの後に実行したい処理を記述する。
3.3 補足
(1) Given、When、ThenはdefineStepのエイリアス
Given、When、Thenは全て同じメソッドのエイリアスなので、featureで利用する際はどれで使っても良いです。
ただし、わかりやすくするためにもできる限り用途ごとに使い分けると良いと思います。
(2) setDefaultTimeout()でタイムアウト時間を設定する
”milliseconds”でテストのタイムアウト時間を設定することができます。
setDefaultTimeout(milliseconds);
(3) stepDefinitionのステップに対する処理では最後にdone()を呼ぶ
When(/^"(.*)"から"(.*)"を選択する$/, function(cssName, value, done){
co(function*() {
yield element(by.css(cssName)).element(by.cssContainingText('option', value)).click();
done()
});
});
(4) 日本語でテストを書く
featureファイルの最初に以下のコードを書く。
# language: ja
以下のコマンドで英語と日本語の対応を確認することができる。
$ cucumber --i18n ja
4. Chaiの使い方
よく利用するAPIと私がつまづいた部分の補足をします。
4.1 よく利用するAPI
ここで説明しているAPI以外も知りたい場合は次のURLをご覧ください。
http://chaijs.com/api/bdd/
以下で、cssNameは任意のCSSセレクタ、textは任意のテキスト。
(1) 要素が表示されたか(存在しているか)を判定する
exist
expect(element.all(by.css(cssName))).to.exist;
(2) ある文字が文字列に含まれるか、ある要素が配列に含まれるかを判定する
include(text)
expect(element(by.css(cssName)).getText()).to.include(text);
(3) trueになったかどうかを判定する
true
expect(element(by.css(cssName)).isPresent())to.be.true;
(4) 型も含め等しいかどうかを判定する
equal(text)
expect(element(by.css(cssName)).getText()).to.equal(text);
4.2 補足
(1) Chai as promisedについて
Chai as promisedでPromiseがresolveした後の値に対して判定を行う。
Chai as promisedで追加されるeventuallyメソッドを利用する。
expect(element(by.css(cssName)).getText()).eventually.to.include(text);
// cssNameはCSSセレクタ、textは任意のテキスト
(2) toやbeなどはわかりやすさのために使うのみで、実際的な処理は行わない
参照
[1] AngularJS 向けの E2E テストツール「Protractor」で要素を特定するアレコレ
[2] Cucumber のフィーチャの文法 - Gherkin