実践入門 Ember.js

第8回 開発ツール(Ember CLI)

この記事を読むのに必要な時間:およそ 6 分

テストのサポート

Ember CLIはテストの実行をサポートしています。

テスト実行はテスト支援ツールであるtestemによって行われます。これには次の利点があります。

  • 仮想ブラウザ/実ブラウザを含む複数のブラウザでテストが実行できる
  • ソースコードの変更を検知してテストが自動で実行される

また,テストの実行方法は次の2種類が用意されています。

ember test

シェル経由でテストを実行しTAP形式ででテスト結果を出力します。CI環境向けの実行方法です。

ember test --server

テスト用のサーバを起動して,ファイルの変更を検知して自動でテストを実行します。手元で開発しながらテストを実行する際に適した実行方法です。

Ember CLIのデフォルトの設定では,仮想ブラウザであるPhantomJSが利用されます。もしインストールしていなければ次のコマンドを実行してインストールしてください。

$ npm install -g phantomjs

では,次はテストコードを見ていきましょう。

Unitテスト

実はさきほどクラスのひな形を生成した際,Unitテストのひな形も生成されていました。tests/unitディレクトリの中に,クラス毎のテストが生成されています。

ここではtests/unit/routes/index-test.jsを見てみましょう。

import {
  moduleFor,
  test
} from 'ember-qunit';

moduleFor('route:index', {
  // Specify the other units that are required for this test.
  // needs: ['controller:foo']
});

test('it exists', function(assert) {
  var route = this.subject();
  assert.ok(route);
});

新しく登場した部分を解説します。

ember-qunit

QUnitをベースにEmber用のヘルパーが追加されたライブラリです※5⁠。

※5
package.jsonember-cli-mochaを指定することで,テストでQUnitの代わりにMochaを利用可能です。好みで使い分けてください。
moduleFor

ember-qunitが提供するヘルパーです。引数で指定したモジュールをセットアップします。ここではroutes/indexをセットップし,テスト実行中にthis.subject()で取得できるようにします。

また,次のオプションを指定できます。

  • beforeEach:テスト実行前に行う処理を記述する関数を設定します。
  • afterEach:テストを実行後に行う処理を記述する関数を設定します。
  • needs:依存するモジュールを記述すると,テスト実行時にメインのモジュールと一緒にセットップされます。

クラスを拡張していく際,各クラスの振る舞いを検証したくなった際はここにテストを追加していくことになるでしょう。Unitテストについては公式ドキュメントに詳しく紹介されているため,ぜひこちらにも目を通してみることをお勧めします。

Acceptanceテスト

次は,Acceptanceテストを見ていきましょう。Ember CLIのAcceptanceテストは,ユーザが操作するであろうシナリオごとにテストケースを作成します。そしてJavaScriptでアプリケーションを操作しながら検証を行います。

例えば,ブログシステムに対して「記事を投稿できること」というテストケースを想定してみましょう。

まずはテストのひな形を作成します。

$ ember generate acceptance-test post-an-article

このコマンドを実行すると,次のファイルが生成されます。

// tests/acceptance/post-an-article-test.js
import Ember from 'ember';
import {
  module,
  test
} from 'qunit';
import startApp from 'example-app/tests/helpers/start-app';

var application;

module('Acceptance: PostAnArticle', {
  beforeEach: function() {
    application = startApp();
  },

  afterEach: function() {
    Ember.run(application, 'destroy');
  }
});

test('visiting /post-an-article', function(assert) {
  visit('/post-an-article');

  andThen(function() {
    assert.equal(currentPath(), 'post-an-article');
  });
});

ポイントは次の通りです。

  • beforeEachでアプリケーションを生成afterEachでアプリケーションを破棄することで,テストケース毎に新しいアプリケーションを利用しています。

  • 実際にユーザが行うであろう操作をエミュレートしています。

    ここではvisit()メソッドを使って,画面遷移を行っています。このメソッドはEmber Test Helpersによって提供されています。

Ember Test Helpersが提供するメソッドは次の通りです。

「アプリケーションに対して操作を行うメソッド」

次のメソッドは非同期処理で実行されます。これは,アニメーションや画面遷移など時間のかかる操作が挟まった場合でも,メインスレッドをブロックすることなく指定した操作を順番を行うための工夫です。

visit(url) 引数で指定したurlに遷移します。
fillIn(selector, text) selectorで指定したDOMに対して,value属性にtextを設定します。フォームを埋める際に利用します。
click(selector) selectorで指定したDOMに対してクリックイベントを発火させます。リンクやボタンをクリックする際に利用します。
keyEvent(selector, type, keyCode) selectorで指定したDOMに対してtype(mousemoveやkeypressなどの)キー操作のイベントを発火させます。その際,keyCodeを指定可能です。
triggerEvent(selector, type, options) selectorで指定したDOMに対してtypeのイベントを発火させます。イベントはjQery.Eventオブジェクトとして発火します。optionsjQery.Eventにそのまま渡されます。
「アプリケーションに対して確認を行うメソッド」

次のメソッドは同期処理で実行されます。アプリケーションに対しての確認は何かをブロックすることはないため,同期的にメソッドが実行されます。

find(selector, context) selectorで指定したDOMを取得します。contextを指定すると,特定のDOMの子要素からのみ探索されます。
currentPath() 現在のURLのパスを取得します。
currentRouteName() 現在のRoute名を取得します。
currentURL() 現在のURLを取得します。

同期実行のメソッドと非同期実行のメソッドを混在させたテストシナリオを作成する場合,どのようの扱えばよいのでしょうか? 特に気にせず処理を混在させてしまうと, 操作が完了する前に検証を行ってしまいテストに失敗する場合があります。そのため,同期処理で確認を行う際は以下のように非同期処理が完了するのを待ちます。

andThen(function() {
  // ここで確認を行う
});

andThenはスケジュールされている非同期処理の完了を待ってからコールバックを実行する関数です。

以上を踏まえて,⁠記事を投稿する」というテストケースを組み立ててみると次のようになります。

// tests/acceptance/post-an-article-test.js
import Ember from 'ember';
import {
  module,
  test
} from 'qunit';
import startApp from 'example-app/tests/helpers/start-app';

var application;

module('Acceptance: PostAnArticle', {
  beforeEach: function() {
    application = startApp();
  },

  afterEach: function() {
    Ember.run(application, 'destroy');
  }
});

test('Post an article', function(assert) {
  visit('/post/new');
  fillIn('input.title', 'Ember CLIについて');
  fillIn('input.body', '今回はAcceptanceテストの紹介です。');
  click('input.submit');

  andThen(function() {
    assert.equal(find('.alert-success').text(), '記事を作成しました');
  });
});

以上,Acceptanceテストの解説でした。

著者プロフィール

佐藤竜之介(さとうりゅうのすけ)

株式会社えにしテック所属。JavaScriptとRubyが好きなウェブ系プログラマ。オープンソースソフトウェアに強い関心がありEmber.jsにも数多くのパッチを送っている。

ブログ:http://tricknotes.hateblo.jp/
Twitter:@tricknotes