ユニットテストをしてみよう
AngularJSはDOM要素とプログラムを切り離して実行が可能なために、比較的テストが行いやすいフレームワークになっています。実際にどのような流れでテストが行えるのかを紹介します。
Jasmineをつかってみる
Jasmineは、AngularJSのドキュメントでも、紹介されているユニットテストのフレームワークです。
もう一つ、karmaというテストフレームワークも紹介されていますが、こちらは、コマンドラインからJavaScriptを実行する事ができる環境で、Jasmineなどと組み合わせてテストを行うことができます。
ただし、今回はブラウザだけで実行が可能なようにJasmineのみでのテスト方法を紹介します。
AngularJS-mockとJasmineをダウンロードする
AngularJSでユニットテストをする際には、ngMockという別のモジュールが必要になります。ngMockは「angular-mocks.js」というプログラムに含まれていますので、ダウンロードサイト から別途ダウンロードします。
または、連載の初回で紹介したAngularJSのzipファイルにも含まれていますので、そちらを利用しても問題ありません。今回はサンプルプログラムとAngularのバージョンを合わせるために1.2系を利用しています。
続いて、Jasmineはgithub上のjasmine-standalone-x.y.z.zipファイルをダウンロードします。今回は、2.1.3のバージョンであるjasmine-standalone-2.1.3.zipを使用します。
テストプログラムを用意する
プロジェクトの配下にtestフォルダを作成し、先ほどダウンロードしたjasmine-standalone-2.1.3.zipファイルを、その配下に解凍します。できあがったフォルダ構造は図1のようになります。また、テストサンプルを含んだHTMLファイル(SpecRunner.html)ができあがりますので、リスト6のように修正してテストを実施します。
<script src="lib/jasmine-2.1.3/jasmine.js"></script> <script src="lib/jasmine-2.1.3/jasmine-html.js"></script> <script src="lib/jasmine-2.1.3/boot.js"></script> <!-- (1)Angularとアプリで使用するソースを記述 --> <script src="../vendors/angular.js"></script> <script src="../vendors/angular-mocks.js"></script> <script src="../js/app.js"></script> <script src="../js/controllers.js"></script> <script src="../js/services.js"></script> <script src="../js/service/ArrayItems.js"></script> <!-- (2)実際のテストコード --> <script src="spec/TodoSpec.js"></script>
(1)ではAngularJSのコードとngMock、そして、これまで作成したコードを指定します。(2)はテストをするためのコードで、解凍するとサンプルとして存在するPlayerSpec.jsなどを参考に記述していきます。
テストプログラムの概要
jasmineを使ったユニットテストのためのプログラムは、リスト7のようになります。describeメソッドで、テストをするグループのまとまりを作成します。続いて、beforeEachメソッドを使ってテストを実施する前の事前準備を記述します。beforeEachメソッドはいくつ記述しても問題ありません。続いて、itメソッドを使って、テストを実施します。このような構造をテストの個数分だけ記述していきます。Jasmineの詳しい使い方は他の記事にもあるので、そちらを参照するとよりわかります。
describe("テストの説明",function(){ // (1) 大きなテストのくくり
beforeEach(function(){
// 事前準備
});
it("テスト項目1",function(){
// 実際のテスト処理を記述
// アサーションメソッドを使って値をチェック
expect(1).toEqual(1);
})
});
AngularJSのモジュールやコントローラを使うには事前準備をする必要があります。
AngularJSでのテストコード
サンプルで作成したpageController内に定義したメソッド(リスト8)のテストコードを作成していきます。
$scope.showMessage = function(msg){
$scope.message = msg;
$timeout(function(){
$scope.message.show = false;
},3000);
};
AngularJSのコードを実行するためには、モジュールの作成やテストするコントローラの作成など、おおよそ同じような前処理を記述する必要があります。実際にテストコードを記述したコードがリスト9です。
describe("ToDoサンプルのテスト", function () {
// (1)DIで設定する変数
var $rootScope, $timeout, $controller, Items;
var controller, $scope;
// (2)モジュールをロードする
beforeEach(module('todoModule'));
// (3)DIを用いて、必要なサービスを設定する
beforeEach(inject(function (_$controller_, _$rootScope_, _$timeout_, _Items_) {
$rootScope = _$rootScope_;
$controller = _$controller_;
Items = _Items_;
$timeout = _$timeout_;
$scope = $rootScope.$new(); // (4)$scope変数を$rootScopeから作成
controller = $controller('pageController', {
$scope: $scope, $timeout: $timeout, Items: Items
}); // (5)pageControllerを作成
}));
it('messageのテスト', function () {
var msg = {
type: 'alert-warning',
text: '削除しました',
show: true
};
// (6)pageController内の$scope.showMessageを実行
$scope.showMessage(msg);
// (7)show == true で有ることを確認
expect($scope.message.show).toEqual(true);
// (8)$timeoutに予約されている処理を強制的に実行
$timeout.flush();
// (9)show == false に変更されている事を確認
expect($scope.message.show).toEqual(false);
});
});
(1)では、DI機能を使って取得したオブジェクトを保存するための変数を用意します。続いて、(2)ではmodule関数を使ってtodoModuleをロードしています。(3)では(5)で呼び出すpageControllerの引数に必要なサービスを設定します。
inject関数はコントローラを定義するときの引数名からサービスを設定することと同じようなことを行います。ただし、$scopeオブジェクトだけは(4)のように$rootScopeから$newメソッドを作って作成します。実際にテストを行う処理が(6)~(9)のようになります。
$timeout内で3秒後に値を変える処理がありますが、(8)のように$timeout.flush()を使用すると即時に処理が実行されるので、実際の時間を待たずに処理が確認できます。
injectメソッドはAngularJSのDI(依存性注入)機能と同じように使えます。
従って、$rootScopeを使いたい場合に、引数に$rootScopeを記述すると、自動的に設定されます。しかしながら、変数名が同じだとテストコードを記述しにくいなどの理由から、_$rootScope_のように変数名の両側に"_"(アンダースコア)を記述した方式を推奨しています。
最後に
これまでサンプルコードを通じてAngularJSの開発からテストまでの流れを紹介してきました。多少難しく感じる部分があるかと思いますが、現時点ではあまり気にする必要はありません。何回かコードを記述していると同じような記述方法になっていると気がつくはずです。そうなれば、後はリファレンスを見ながらコードが記述できるようになります。次回からは、ディレクティブなど機能毎の使い方を説明します。

