PhantomJSとJasmineで振る舞い駆動開発なJavaScriptテスト:フレームワークで実践! JavaScriptテスト入門(2)(2/3 ページ)
しっかりとJavaScriptをテストするために、今注目のJavaScript用のテストフレームワークをいくつか紹介し、その概要から実践的な使い方まで解説する連載
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公式サイトなども参考にしてください。
*** 一部省略されたコンテンツがあります。PC版でご覧ください。 ***
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'
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.