require.js 環境で mocha + expect + testem を使った JavaScript テスト

先日書いた自分用アプリケーションのひな形

これに、JavaScript のテスト環境も追加したい。

結論からいくと、フレームワークには mocha + expect、ランナーは testem を使うことにした。ついでにテストダブルライブラリとして Sinon.js も有効にした。

ちなみに今回の文脈は End to End のテストではなくてユニットテスト周りのおはなしです。

mocha + expect

JavaScript のこの辺のテスト周りは今もいろいろなツールの整備が進んでいて、今回採用した以外にも Jasmin や QUnit そのほか色んな物がある。昨今の状況に関しては 先日の HTML5とか勉強会のレポート が詳しい。

今回 mocha + expect.js を使うことにしたのは、@teppeis さんが WEB+DB PRESS の Vol.73 号で、あと @hokaccha さんが神プレゼンの中でおすすめしていたのが主な動機。モック、アサーションフレームワークが全部入りな Jasmin も良いけど、それぞれ個別に選択するほうが好み、つまり agnostic なほうが良い場合は mocha をベースにするのが良さそうだと思った。

実際 mocha + expect.js を使ったテストはどんな感じになるかといえば

define ->
  describe 'テスト', ->
    it 'が可能であること', ->
      expect(true).to.be true

    it 'でspyが使えること', ->
      spy = sinon.spy()
      spy()
      expect(spy.calledOnce).to.ok()

こんな感じです。これは mocha の BDD スタイル。

testem

これを testem という、Node.js ベースのリモートランナーで実行する。testem を使うと、mocha + expect で書いたテストを複数のブラウザをまとめて立ち上げてテストさせることができる。操作にあたって面倒な手続きはなく、コマンドラインから testem を実行するだけ。testem が ChromeFirefox をまとめて立ち上げてテストしてくれるし、ローカルファイルの更新を検知して socket.io 経由でテストを再実行してブラウザ上の結果を動的に更新してくれたりする。

testem の出力は CI 向けに調整することもできて、Jenkins はもちろん、Travis CI でも継続的インテグレーションさせることができるそうです。

JavaScript のブラウザ上でのテスト、というとだいぶ前に Selenium を使って、結構面倒だなあという印象を持っていたけど、ずいぶんと洗練されたものだなあと思いました。

require.js といっしょに使う

ところでこの mocha + expect + testem を、アプリケーションもテストも require.js を使っているという前提で動かそうとして結構はまった。試行錯誤の上落ち着いた結果を今のレポジトリに反映してあります。

コツとしては、普通は testem がブラウザでテストを実行するようの html を吐いてくれるところ、それを testem 任せにしないで自分で書く点、require.js の config で baseUrl 含めうまく設定を調整する点・・・ですかね。どの言語でもそうですが、慣れないとこういうライブラリパス周りは本当によく嵌る。

html を自分で書かないと、testem が自動生成するファイルでは mocha を走らせるタイミングやスコープが微妙で、require.js でロードしたライブラリのテストがうまく実行できない。結局 slim でこういうランナー向け html を書いて

DOCTYPE html
html
  head
    title Test'em
    link rel="stylesheet" href="/testem/mocha.css"
    script src="/public/js/vendor/mocha/mocha.js"
    script src="/public/js/vendor/expect/expect.js"
    script src="/public/js/vendor/sinon/index.js"
    script mocha.setup('bdd')
    script src="/testem.js"

    /  data-main は require.config を共有するために読み込む
    script src="/public/js/vendor/requirejs/require.js" data-main="/public/js/main"

  body
    #mocha

    javascript:
      var files = [];
      {{#serve_files}}files.push("../{{src}}");{{/serve_files}}
      require(files, function() {
        mocha.run();
      });

testem の設定は以下にすることで、問題を回避。

framework: mocha
test_page: test/runner.html
launch_in_dev:
  - Chrome

src_files:
  - test/**/*.js

HTML の中から mustache (かな?) でテンプレート変数を参照しているところがポイントですね。というか、これも @teppeis さんに教えてもらったのですけど。

後は細かいバッドノウハウとして Sinon.js が bower で拾ってくるとブラウザ用にビルドされてなくて嫌んな感じだったので bower.json

{
    "name": "My Application",
    "version": "0.0.1",
    "dependencies": {
        "requirejs": "latest",
        "jquery": "latest",
        "backbone-amd": "latest",
        "underscore-amd" : "lastest"
    },
    "devDependencies": {
        "mocha": "latest",
        "expect": "latest",
        "sinon": "http://sinonjs.org/releases/sinon-1.7.1.js"
    }
}

と URL を直打ち。bower は bower レポジトリにないスクリプトもこの方法でいちおう管理することができるようになっている。

これで、テストを test ディレクトリ以下に追加すると、Grunt で自動でコンパイルしたのを testem が走らせるという流れになる。TDD の環境が完成しました。

Grunt を使う

テスト周りの環境を整えたら、CoffeeScript のファイル数が増えたりして Sinatra 任せのコンパイルではちょっとワークフロー的に難しくなってきたので、Coffee のコンパイルはもう Grunt に任せるようにした。

ここに Gruntfile があるので似たようなことをしたい、という方の参考にでもなれば幸い。

こんな感じでテストの環境も自分的にはまあモダンな感じで取りそろえることができたし、結構満足している。いや、ここで満足しないでアプリケーション書きましょうという話ですね。はい。ウッ、Backbone.View のテスト難しい・・・地球のみんな、オラに js 力を・・・。

そうそう、途中さらっと流しましたが @hokaccha さんの http://hokaccha.github.io/slides/javascript_design_and_test/ このプレゼン資料は大変に良資料ですので激しくオススメしておきます。WEB+DB Vol.73 にもこの辺のツールのことが詳しく書かれておりますことですよ。

WEB+DB PRESS Vol.73
WEB+DB PRESS Vol.73
posted with amazlet at 13.05.09
設樂 洋爾 白土 慧 奥野 幹也 佐藤 鉄平 後藤 秀宣 mala 中島 聡 堤 智代 森田 創 A-Listers はまちや2 大和田 純 松田 明 後藤 大輔 ひろせ まさあき 小林 篤 近藤 宇智朗 まかまか般若波羅蜜 Mr. O
技術評論社
売り上げランキング: 28,155