単体テストの件数や可否ももちろんですが、コードのカバー率も可視化されることで、品質向上の1つの指標にもなります。
Node.jsで開発しているプロジェクトについて、こういったデータをgruntタスクで簡単に生成できるようにしましょう。
仕組みとして使うものは以下のとおりです。
- grunt: JSタスクランナー
- mocha: JSテストフレームワーク
- chai: BDD/TDDアサーションライブラリ
- sinon: Spy,Stub,Mockライブラリ
- istanbul: コードカバレッジ計測ツール
gruntは既に導入済みで、活用している前提とします。
JSのテストはJasmineが有名ですが、自由度の高いmocha+chai+sinonが個人的にはお気に入りなのでこちらを使います。
コードカバレッジ計測はいくつかの選択肢がありますが、メソッドや行、分岐等を計測できるistanbulを使います。
流れとしては、
- 関連パッケージをインストールする
- Gruntfileを設定する
- mocha+chai+sinonでテストを書く
- gruntタスクからistanbul経由でmochaを実行、コードカバレッジ情報を出力する
となります。
関連パッケージをインストールする
まずは、mochaとistanbulのインストール。
grunt-mocha-istanbulというgruntタスクのパッケージがあるのでこちらを使います。
npm install --save-dev grunt-mocha-istanbul
続いて、chai+sinonのインストール。
こちらは、sinon-chaiというパッケージを入れます。
これにより、Sinon.JSのspy,stub,mock系メソッドががchaiのassertionとして使えるようになります。
npm install --save-dev sinon-chai
後述しますがCoffeeScriptでテストを書きたいので、grunt-contrib-coffeeやgrunt-coffee-jshintも入れておきましょう。
npm install --save-dev grunt-contrib-coffee grunt-coffee-jshint
Gruntfileを設定する
istanbulはCoffeeScriptを直接解釈できないため、インストールしたgrunt-contrib-coffeeやgrunt-coffee-jshintを利用して、
- CoffeeScriptでコードを書く
- jsHint後CoffeeScript->JavaScript変換(ビルド)
- ビルド後のコードでテスト実施
となるようにGruntfile.coffee
を設定します。
ibrikというistanbulのCoffeeScript版を使う手もありますが、mocha連携等が上手く行きませんでした。
coffee_jshint設定
test以下のコードに対してjsHintによるチェックをかける設定です。
残念ながらmocha、chai、sinonに関してはjsHintのオプションが無いため、globalsに使用するものを列挙しておきます。
ついでにGruntfile.coffeeのチェックも仕掛けておきます。
coffee_jshint:
gruntfile:
options:
jshintOptions: [ 'node' ]
src: 'Gruntfile.coffee'
test:
options:
jshintOptions: [ 'node' ]
globals: [ 'after', 'afterEach', 'before', 'beforeEach', 'describe', 'it', 'expect', 'sinon' ]
src: 'test/**/*.coffee'
coffee設定
CoffeeScript->JavaScript変換の設定です。
test
以下のファイルを変換し、build/test
以下に出力します。
ファイル名に「.」(ドット)が含まれると誤動作する問題があるので、renameを独自に定義しておきます。
coffee:
test:
options:
bare: true
expand: true
cwd: 'test'
src: '**/*.coffee'
dest: 'build/test'
rename: (dest, src) ->
dirname = src.replace(/[^\/]*$/, '')
basename = src.replace(/.*\//, '').replace(/\.[^.]*$/, '')
"#{dest}#{dirname}/#{basename}.js"
mocha_istanbul設定
テストとコードカバレッジ計測の設定です。
build/test
以下の*.spec.js
をテストとして実行し、lcovフォーマットで出力するよう設定します。
mocha_istanbul:
test:
src: 'build/test'
options:
mask: '**/*.spec.js'
reportFormats: [ 'lcov' ]
testタスクを作成
grunt.registerTask
でtestタスクを作成し、一連のタスクが実行されるようにします。
grunt.registerTask 'test', 'run test and generate coverage information', [
'coffee_jshint:test'
'coffee:test'
'mocha_istanbul:test'
]
grunt-contrib-watchを使用しているのであれば、以下の様なwatchタスクを作成してもいいかもしれません。
watch:
gruntfile:
files: '<%= coffee_jshint.gruntfile.src %>'
tasks: 'coffee_jshint:gruntfile'
test:
files: '<%= coffee_jshint.test.src %>'
tasks: 'test'
mocha+chai+sinonでテストを書く
単体テストの書き方は調べれば沢山出てきますので、ここでは割愛します。
mocha+chai+sinonでテストコードを書く場合は、個人的にはCoffeeScriptで書くことをオススメします。
余計な括弧がなくなることで、テストのコードがかなり見やすくなります。
例えば、以下のようなイメージでtestディレクトリ以下にテストコードを設置します。
テスト対象を用意するのが面倒なので、とりあえずunderscore.jsを対象として書いてみたサンプルです。
chai = require 'chai'
expect = chai.expect
sinon = require 'sinon'
chai.use require('sinon-chai')
_ = require '../src/underscore'
describe 'Underscore', ->
describe 'main', ->
it 'should be a function', ->
expect(_).to.be.a 'function'
describe '#bind', ->
it 'should be a function', ->
expect(_.bind).to.be.a 'function'
it 'should return a bound function', ->
func = sinon.spy()
obj = {}
newfunc = _.bind func, obj, 'sample'
expect(newfunc).to.be.a 'function'
expect(func).to.have.not.been.called
newfunc()
expect(func).to.have.been.calledOnce
expect(func).to.always.have.been.calledOn obj
expect(func.firstCall.args[0]).to.equal 'sample'
最後に、underscore.jsをbuild/src
以下に設置しておきます。
本来ならsrc/*.coffee
等を作成してbuild/src
以下にビルドする感じになりますが、とりあえずということで。
mkdir -p build/src
curl -o build/src/underscore.js http://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.6.0/underscore.js
gruntでtestタスクを実行してみる
grunt test
を実行して、以下の様な結果が表示されればOKです。
coverageディレクトリが生成されていますので、その中のlcov-report/index.htmlを開くと、coverage情報をブラウザ上で確認することができます。
通過した部分が緑、未通過の部分が赤で表示されていることがわかります。
Node.jsはFunctionオブジェクトにnativeでbindメソッドを持っているため、612行目でreturnしてしまっています。
全てを必ず100%にする必要は無いですし、カバレッジを100%にしたとしてもテストコードが「通過した」というだけに過ぎず、必ずしもコードの品質が上がるわけではありません。
しかしながら、カバレッジデータを見ながらテストコードを書くことで、未検証のコードが明らかになり、効率よくテストコードを書いていくことができるようになるのは非常に大きいと思いますので、是非やってみましょう。