連載
» 2012年10月10日 18時00分 公開

PhantomJSとJasmineで振る舞い駆動開発なJavaScriptテストフレームワークで実践! JavaScriptテスト入門(2)(2/3 ページ)

[竹澤陽,TIS コーポレート本部 戦略技術センター]

JavaScript用振る舞い駆動開発フレームワーク「Jasmine」

 JasmineはJavaScript用の振る舞い駆動開発(BDD)テストフレームワークです。以下に記述例を示しますが、Rubyのテストフレームワーク「RSpec」と良く似た構文を採用しており、RSpecの利用経験があれば、すんなり適応できるかと思われます。

describe("A suite", function() {
    it("contains spec with an expectation", function() {
        expect(true).toBe(true);
    });
});

 Jasmineでは、値が正しいかのテスト、例外の有無のテスト、関数呼び出しが行われたかどうかのテストなど、さまざまなテストができるようになっています。

 また、最近のJavaScriptではsetTimeout/setIntervalやAjax呼び出しが使われることが多くなっていますが、Jasmineでは時計をMockで置き換えることで高速なテストを行ったり、非同期呼び出しの終了を待つメソッドが用意されているなど、非同期型処理に対するテストも考慮した作りになっています。

 本稿ではJasmineのすべては解説できないので、テストの書き方についてはJasmine公式サイトなども参考にしてください。

Jasmineをインストールする

 公式サイトの最下部左側にダウンロード用のURLが記載されています。今回はStandalone版を利用しますが、Ruby Gem版も開発されています。既存のRailsプロジェクトにJavaScriptテストを追加する場合にはGithubの「Jasmine Gem」を参考に設定を行なってください。

 Standalone版はその名の通り単独で動作するため、ダウンロードして解凍するだけで動作可能です。

$ wget https://github.com/downloads/pivotal/jasmine/jasmine-standalone-1.2.0.zip
# 圧縮時にディレクトリを含まずに圧縮されているため、-d必須
$ unzip jasmine-standalone-1.2.0.zip -d jasmine
$ cd jasmine

Jasmineのサンプルを動作させる

 解凍したJasmineの中にはJasmine本体を格納した「lib」ディレクトリの他に、Jasmineによるテストのサンプルが含まれています。「src」ディレクトリにテスト対象のPlayerクラスとSongクラス、specディレクトリにはテストケースが格納されており、「SpecRunner.html」をWebブラウザで開くことでテストが行われるようになっています。

 Jasmineによるテストはブラウザ上で動作するため、Webサーバを立てたうえでGoogle Chromeでアクセスしてみます。

$ ruby -rwebrick -e 's = WEBrick::HTTPServer.new(:Port=>8080, :DocumentRoot=>Dir.pwd);trap("INT"){s.shutdown};s.start'
ワンライナーWebサーバでカレントディレクトリを公開

 iptablesやAWSのSecurityGroupでポートを制限している場合には設定に応じて変更してください。

 解凍した状態のサンプルでは5つのテストが行われ、すべて成功していると思います。

Jasmineのテストケースを確認して書き換える

 動作確認ができたので、このままPhantomJSによる自動テストに移っても良いのですが、まずはJasmineによる代表的なテストの書き方を確認しつつ、いくつかのテストを失敗するように変更してみます。

Jasmineによるテストケース(PlayerSpec.js)

describe("Player", function() {
    var player;
    var song;
  
    beforeEach(function() {
        player = new Player();
        song = new Song();
    });
  
    it("should be able to play a Song", function() {
        player.play(song);
        expect(player.currentlyPlayingSong).toEqual(song);
    
        //demonstrates use of custom matcher
        expect(player).toBePlaying(song);
    });
・
・
・

 Jasmineにおける基本的な構造は 同種のテストをまとめる関数として「describe」が存在し、その中に実際のテストを記述した「it」関数が1つ、もしくは複数記述される形になっています。

 「describe」「it」はいくらでも入れ子にでき、1つの「it」が1つのテストケースに対応しています。ブラウザからテストを実行した際に5つのテストが実行されましたが、サンプルテストのPlayerSpec.jsを検索してみると対応する5つの「it」を見つけられるかと思います。

 テストケースを実行する際には、その「it」を囲むすべての「describe」に定義された「beforeEach」を順番に動作させ、その後に「it」の中に含まれるテストを実行します。サンプルでは「beforeEach」においてPlayer/Songの初期化を行い、「it」の中で再生開始、およびそのテストを行っています。

 値の検証に使用できるのがexpect、およびその後ろに記述される「toEqual」「toBeXXXX」といった「matcher」です。

 Jasmineでは、値が等しいことを検証する「toEqual」や、Nullであることを検証する「toBeNull」、特定の引数とともに関数が呼び出されたことを確認する「toHaveBeenCalledWith」などさまざまな「matcher」が定義されています。

 ここで使用されている「toBePlaying」はちょっと特殊で、「Jasmineで定義されていない『toBeXXXX』という『matcher』はテスト対象の『isXXXX』を検証に使用する」というルールを用いています。そのため、ここではPlayer.jsに定義されている「isPlaying」の結果がtrueかどうかをテストしていることになります。

 今回はPlayer.jsでisPlayingを変更している個所を書き換え、このテストを「fail」にさせます(失敗させます)。

@@ -2,7 +2,7 @@
 }
 Player.prototype.play = function(song) {
   this.currentlyPlayingSong = song;
-  this.isPlaying = true;
+  this.isPlaying = false;
 };

 Webブラウザ側をリロードすると、2個のテストがfailしたことが分かります。片方はtoBePlayingによってisPlayingの値をチェックしていた個所、もう片方は再生中にPlayer#resumeを呼び出すことで例外が発生するのを期待している個所です。

 後者がfailした理由はPlayer#resumeを見ると分かります。isPlayingを条件として例外の有無を切り替えているため、isPlayingを書き換えたことで例外が発生しなくなってしまったようです。

 さて、この2個のテストは、どちらもexpectでfailしているため、Player.jsの以下の個所を書き換え、実行中に例外が発生する例も作っておきます。

@@ -19,4 +19,5 @@
 
 Player.prototype.makeFavorite = function() {
   this.currentlyPlayingSong.persistFavoriteStatus(true);
+  throw new Error("Throw Error in src");
 };

 もう1度Webブラウザ側をリロードすると、以下の3個のテストがfailしたことが分かります。

  • Player should be able to play a Song.
  • Player tells the current song if the user has made it a favorite.
  • Player #resume should throw an exception if song is already playing.

 ここまでは、Webブラウザからテストを実行してきましたが、連載第1回でも触れたとおり、Webブラウザの存在は自動テストや継続的インテグレーション(CI)と相性が良くありません。

 ここからは本記事の最終目的、ヘッドレス型のテストに向けてPhantomJS(QtWebKit)によるテストを行っていきます。

Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。