Jasmine で tDiary の JavaScript をテストする
tDiary に jQuery がバンドルされるようになって、 JavaScript を使った tDiary プラグインも増えてきた。 jQuery をバージョンアップすると動かなくなるプラグインが出てきたので、 JavaScrpit 部分のテストをちゃんと書くことにした。
少し調べると、 QUnit と Jasmine が候補で見つかった。
Jasmine は RubyKaigi2010 で紹介されていた記憶があるな。つーか、自分の日記にもしっかり書いてあった。
午後は前述の @sarahmei さんに jasmine についていろいろと質問。 jasmine は JavaScript 用のテストフレームワーク。 RSpec 風に書けるのが特徴。 esm さんのセッションでも使っていた pivotal tracker の開発元 (Pivotal Labs) がメンテしている。 調べてみたら、 Pivotal Labs は昔 JSUnit のメンテをしていたみたいで、いまは jasmine に移行しているようだった。 午前中に jasmine を使って tDiary の下書きプラグインのテストを書いていたんだけど、そのときに出てきた疑問を質問した。
Q. $(function() { var Draft; }) と、 jQuery の ready 関数内で定義したオブジェクトのテスト方法は? A. グローバルにtDiary.Draftと定義するといいよ。(プロダクト名をグローバルな名前空間として使う)
Q. jQuery のような外部ライブラリを組み込んでテストするには? A. jasmine.ymlの
src_files
で指定する。ただし http:// 指定は不可で、ローカルに jQuery.js を配置する必要あり。 理由を聞いたらsrc_files
にはdist/**/*.js
のようにワイルドカード指定を許容しており、これを Dir.glob で読み込んでいるかららしい。Q. jasmine はモデルのテストを書けるようだけど、ビューのテストは可能? A. jasmine-jquery の fixture を使えば、 HTML の断片を組み込むことができる。
おかげでだいたい、 jasmine の使い方が分かった。 つたなすぎる英語での質問だったけど、根気よくかつ丁寧に答えてくれた @sarahmei さんに感謝。
2年も経つとそんなこともすっかり忘れているもので、僕の MacBook Pro 内の tdiary-core ディレクトリに spec/javascript
を見つけて「いつの間に tDiary に JavaScript テストが組み込まれたんだろう?」と不思議に思っていた。犯人は2年前の僕でした。日記は付けておくものだね。
あれから2年経って Jasmine のバージョンも1.0から1.2に変わっている。改めて Jasmine を tDiary に組み込んで、今度はちゃんと pull request を送った。 QUnit ではなく Jasmine にしたのは、「gemで配布されていて ruby と親和性がある」「RSpec風の構文が使える」の2点。
Jasmine のインストール
gem install jasmine
でもいいんだけど、ライブラリの依存関係を管理するのが面倒なので Bundler を使う。 Gemfile に gem 'jasmine'
と書いて bundle install
を実行。インストール後に jasmine init
を実行すると、ジェネレータによってテストのひな形が生成される。
$ jasmine init
Jasmine has been installed with example specs.
To run the server:
rake jasmine
To run the automated CI task with Selenium:
rake jasmine:ci
初期のディレクトリ構成はこうなっている。
$ tree
.
├── Rakefile
├── public
│ └── javascripts
│ ├── Player.js
│ └── Song.js
└── spec
└── javascripts
├── PlayerSpec.js
├── helpers
│ └── SpecHelper.js
└── support
└── jasmine.yml
6 directories, 6 files
特に重要なのは、以下3つのディレクトリ。
- public/javascripts … テスト対象となる JavaScript ソース
- spec/javascripts … テスト (spec)
- spec/javascripts/helpers … テスト用のヘルパー
これらディレクトリの場所は、 spec/javascripts/support/jasmine.yml
で変更可能。
テストの実行
rake コマンドを使ってテスト用のサーバを起動する。
$ bundle exec rake jasmine
your tests are here:
http://localhost:8888/
[2012-09-02 11:44:02] INFO WEBrick 1.3.1
[2012-09-02 11:44:02] INFO ruby 1.9.3 (2012-02-16) [x86_64-darwin11.3.0]
[2012-09-02 11:44:02] INFO WEBrick::HTTPServer#start: pid=70285 port=8888
Web ブラウザでこのサーバに接続すると、 spec/javascript
に配置したテストが実行され結果が表示される。
テスト結果として Player オブジェクトの spec が表示されている。 rspec の -fd オプションに似ている。
この画面の HTML ソースを開くと、以下の順番で処理されていることが分かる。
- jasmine のフレームワーク (jasmine.js, jasmine.css) を読み込んで初期化
- public/javascripts/*.js のテスト対象を読み込む (サンプルでは Player.js と Song.js)
- spec/javascripts/helpers/*.js のヘルパーを読み込む(サンプルでは SpecHelper.js)
- spec/javascripts/*.js の spec ファイルを読み込む(サンプルでは PlayerSpec.js)
- onload イベントを契機に
jasmineEnv.execute();
を実行
Ruby 部分の役割
だいたい Jasmine の動きが分かってきた。 Jasmine 本体は JavaScript で動いていて、テスト環境を用意するところを Ruby が担っている。 Ruby 部分はこんなところか。
jasmine init
によるテストひな形の生成rake jasmine
によるテストサーバの起動- テストサーバの役割は Jasmine 本体とヘルパー、 spec ファイルの読み込み
rake jasmine
の代わりに rake jasmine:ci
を実行すると、テストサーバだけでなく Web ブラウザも起動し、テスト結果をコンソールに表示してくれる。また、テストサーバの動作(各種ディレクトリの場所)は、spec/javascripts/support/jasmine.yml
で変更できる。
Jasmine は(Rubyを使わない)スタンドアロン版も配布されている。スタンドアロン版ではテストサーバ相当の HTML (SpecRunner.html) を自分で書けばよさそう。
tDiary への組み込み
こんな手順で tDiary に Jasmine を組み込んだ。
- Gemfile に Jasmine を追加
- Rakefile (
tdiary/tasks/jasmine.rake
) にjasmine
とjasmine:ci
タスクを追加 - ローカルにテスト用の jquery.js を配置
- jasmine.yml を編集
最初に jQuery を読み込むように src_files
で明示的に指定している。でないと、jQuery に依存する JavaScript ファイルがエラーとなる。
src_files:
- js/jquery-1.8.js # load first
- js/00default.js
テスト内容はこんな感じ。 RSpec 風なのでそれほど悩まない。
describe("$tDiary", function() {
beforeEach(function() {
// 毎回実行する処理はここに書く
});
it("should exist a global $tDiary object", function() {
expect($tDiary).toEqual(jasmine.any(Object));
expect($tDiary.plugin).toEqual(jasmine.any(Object));
});
});
describe("$", function() {
describe("#makePluginTag", function() {
describe("when Wiki style", function() {
beforeEach(function() {
$tDiary.style = 'wiki';
});
it('should create wiki style plugin tag', function() {
var tag = $.makePluginTag("plugin_name", ["param1", "param2"]);
expect(tag).toEqual('{{plugin_name "param1", "param2"}}');
});
});
});
});
ここまでは DOM に依存しないテストだった。 DOM に依存する時は spec にて createElement で書く必要がある。 でもそれは面倒なので、次は jasmine-jquery
を使って外部の HTML ファイルを読み込めるようにするか。