Grunt で Jasmine を使ったテスト環境を作る


たまにしか書かない JavaScript でもちゃんとテストを書く習慣を身につけたいと思い、 Grunt で Jasmine によるテスト実行環境を作ってみた。

`grunt-contrib-jasmine` を使う。

gruntjs/grunt-contrib-jasmine
https://github.com/gruntjs/grunt-contrib-jasmine

## 準備

プロジェクト用のディレクトリを作成して grunt とプラグインをインストール。

```
% mkdir -p jasmine-test/{src/js,spec} && cd jasmine-test
% npm init
% npm install grunt grunt-contrib-jasmine --save-dev
```

これでディレクトリ構造は以下のようになる。

```
jasmine-test
 ├── node_modules
 │     ├── grunt
 │     └── grunt-contrib-jasmine
 ├── package.json
 ├── spec
 └── src
        └── js
```

## JavaScript のテスト

テスト対象の javascript ファイルを作成する。

```javascript:src/js/foo.js
var Foo = (function() {
    function Foo() {}
    Foo.prototype.name = function() {
        return 'Foo';
    };
    return Foo;
})();
```

対応する spec ファイルを作成。

```javascript:spec/foo.spec.js
describe('Foo', function() {
    var foo;
    beforeEach(function() {
        foo = new Foo();
    });

    it('should return name', function (){
        expect(foo.name()).toEqual('Foo');
    });
});
```

Gruntfile.js を作成。

```
% vim Gruntfile.js
```

内容は以下のようにした。

```javascript:Gruntfile.js
module.exports = function(grunt) {
    grunt.initConfig({
        pkg: grunt.file.readJSON('package.json'),
        jasmine: {
            all: {
                src: 'src/js/**/*.js',
                options: {
                    specs: 'spec/**/*.spec.js',
                },
            },
        },
    });
    grunt.loadNpmTasks('grunt-contrib-jasmine');
    grunt.registerTask('default', ['jasmine']);
};
```

実行してみる。

```
% grunt
Running "jasmine:all" (jasmine) task
Testing jasmine specs via PhantomJS

 Foo
   ✓ should return name

1 spec in 0.002s.
>> 0 failures

Done, without errors.
```

上手く動いてるっぽい。
試しに返す文字列を変更して再度実行。

```
% grunt
Running "jasmine:all" (jasmine) task
Testing jasmine specs via PhantomJS

 Foo
   X should return name
     Expected 'Bar' to equal 'Foo'. (1)

1 spec in 0.003s.
>> 1 failures
Warning: Task "jasmine:all" failed. Use --force to continue.

Aborted due to warnings.
```

ちゃんとエラーになった。

## CoffeeScript のテスト

次は CoffeeScript に対しても spec を実行できるようにしてみる。

直接 coffee に対して spec を実行する方法もありそうだけど、軽く調べた感じでは見つけられなかったので、一旦 js に変換してから spec が実行されるようにした。

先ほどのプロジェクトディレクトリ下に追加で CoffeeScript 用のディレクトリを作成し、 `grunt-contrib-coffee` をインストール。

```
% mkdir src/coffee
% npm install grunt-contrib-coffee --save-dev
```

これでディレクトリ構造は以下のようになる。

```
jasmine-test
 ├── Gruntfile.js
 ├── node_modules
 │     ├── grunt
 │     ├── grunt-contrib-coffee
 │     └── grunt-contrib-jasmine
 ├── package.json
 ├── spec
 │     └── foo.spec.js
 └── src
        ├── coffee
        └── js
```

テスト対象の CoffeeScript を作成。

```coffeescript:src/coffee/bar.coffee
class Bar
  name: ->
    'Bar'
```

spec も coffee で書いて変換後に実行、としたいところだが、取り敢えず今回は js で作成。

```javascript:spec/bar.spec.js
describe('Bar', function() {
    var bar;
    beforeEach(function() {
        bar = new Bar();
    });

    it('should return name', function (){
        expect(bar.name()).toEqual('Bar');
    });
});
```

`Gruntfile.js` を以下のように変更。
`coffee` の `options` に `bare: true` が無いと変換後の js が無名関数で囲まれて spec から参照できなくなるので注意。

```javascript:Gruntfile.js
module.exports = function(grunt) {
    grunt.initConfig({
        pkg: grunt.file.readJSON('package.json'),
        coffee: {
            compile: {
                options: {
                    bare: true,
                },
                files: [{
                    expand: true,
                    cwd: 'src/coffee',
                    src: ['*.coffee'],
                    dest: 'src/js/',
                    ext: '.js',
                }]
            }
        },
        jasmine: {
            all: {
                src: 'src/js/**/*.js',
                options: {
                    specs: 'spec/**/*.spec.js',
                },
            },
        },
    });
    grunt.loadNpmTasks('grunt-contrib-coffee');
    grunt.loadNpmTasks('grunt-contrib-jasmine');
    grunt.registerTask('default', ['coffee', 'jasmine']);
};
```

実行。

```
% grunt
Running "coffee:compile" (coffee) task
>> 1 files created.

Running "jasmine:all" (jasmine) task
Testing jasmine specs via PhantomJS

 Bar
   ✓ should return name
 Foo
   ✓ should return name

2 specs in 0.005s.
>> 0 failures

Done, without errors.
```

上手く動いたっぽい。
あとは以前調べた [Grunt で複数の CoffeeScript を一つの無名関数内にまとめる](http://qiita.com/akishin/items/8454786dc12e534531be) あたりと組み合わせて

1. coffee でコード、spec を書く
1. どちらも js にコンパイルして spec 実行
1. アプリケーションコードを一つの js に結合して無名関数で囲む

とかやれば良さそう。

## 参考

grunt.js - grunt+jasmineな環境作成手順 - Qiita
http://qiita.com/ampersand/items/0811f788accd3651e04a

Jenkins, Grunt, Jasmine で JavaScript のテストを CI - Please Sleep
http://please-sleep.cou929.nu/javascript-ci-with-jasmine-grunt-jenkins.html

coffeescript自動コンパイルでjasmine自動テストなgrunt環境構築 - よしそぶろぐ
http://blog.yoshiso.net/post/63619404740/coffeescript-jasmine-grunt