はじめに
以前、JavaScriptのユニットテスト環境を、Testem(テストランナー)+mocha(テスティングフレームワーク)+chai(アサーションライブラリ)という構成にしていました。
この構成でも特に問題はなかったのですが、Karma for JavaScript test runner - blog.koba04.comを拝見して「Karma良さそう…!」と思い、今はKarma+mocha+power-assert(タスクランナーはGrunt)という構成で落ち着いています。
(ちなみに、power-assertはシンプルなアサーションと、テスト失敗時の詳細かつ直感的なログ出力がとても素敵です><)
この記事は、Karma+mocha+power-assertの構成をどのように構築したかの備忘録です。Node.jsやGrunt、Karmaなどのインストール手順は省略しております。
目的
この構成で実現したいユニットテストの方法としては、テスト対象のjsファイル、もしくはテストコードを記述したjsファイルを変更した際に、Chrome上で自動的にテストを実行したいというものです。
なお、アサーションライブラリにpower-assertを利用する場合は、power-assert用にコードを変換し、その変換後のコードに対してテストを実行する必要があります。
(このあたりの仕様はpower-assertのGithubを参照のこと)
フォルダ構成
今回の記事が対象としているフォルダ構成は下記の通りです。
Root
│ Gruntfile.js
│ karma.conf.js
│ package.json
│ index.html
│
├─css
│ index.css
│
├─js
│ │ index.js
│ │
│ └─vendor
│ jquery-2.1.0.js
│
│
└─test
│ index_test.js
│
└─vendor
power-assert.js
必要なGruntプラグインのインストール
grunt-este-watch
ファイルの変更を検知するプラグイン
npm install grunt-este-watch --save-dev
grunt-karma
KarmaをGruntから実行するためのプラグイン
npm install grunt-karma --save-dev
grunt-espower
espower(power-assert用にコードを変換するツール)のプラグイン
npm install grunt-espower --save-dev
Gruntfile.js
Gruntfile.jsの設定内容は下記の通りです。
ここで定義している処理の流れは
- js/index.jsもしくはtest/index_test.jsが変更・保存されたのを検知する
- index_test.jsをpower-assert用に変換する
-
- の変換後のソース(test/espowered/*.js)を対象にテストを実行する
です。
module.exports = function (grunt) {
'use strict';
var pkg = grunt.file.readJSON('package.json');
grunt.initConfig({
pkg: pkg,
// power-assert用にコードを変換するタスク
espower: {
test: {
files: [
{
expand: true,
cwd: 'test/', // 変換対象のファイルを配置しているディレクトリ
src: ['*.js'], // 変換対象のファイル名
dest: 'test/espowered/', // 変換後のコードを配置するディレクトリ
ext: '.js' // 変換後のファイルの拡張子
}
]
}
},
// ファイルの変更検知用タスク
esteWatch: {
options: {
dirs: ['js', 'test'],
livereload: false
},
js: function (filepath) {
return ['test'];
}
},
// Karma実行用タスク
karma: {
unit: {
options: {
configFile: 'karma.conf.js',
autoWatch: true,
browsers: ["Chrome"],
reporters: ["progress"],
singleRun: true,
keepalive: true
}
}
}
});
// package.json内のdevDependenciesで定義されているパッケージ中『grunt-』で始まるものをロードする
for (var taskName in pkg.devDependencies) {
if (taskName.substr(0, 6) == 'grunt-') {
console.log('load npm task -> ' + taskName);
grunt.loadNpmTasks(taskName);
}
}
grunt.registerTask("default", "esteWatch");
grunt.registerTask('test', ['espower', 'karma']);
};
karma.conf.js
karma.conf.jsの設定内容は下記の通りです。
module.exports = function (config) {
config.set({
// base path that will be used to resolve all patterns (eg. files, exclude)
basePath: '',
// frameworks to use
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter
frameworks: ['mocha', 'mocha-debug'],
// list of files / patterns to load in the browser
files: [
'js/vendor/jquery-2.1.0.js',
'js/*.js',
'test/vendor/power-assert.js',
'test/espowered/*.js'
],
// list of files to exclude
exclude: [
],
// preprocess matching files before serving them to the browser
// available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
preprocessors: {
},
// test results reporter to use
// possible values: 'dots', 'progress'
// available reporters: https://npmjs.org/browse/keyword/karma-reporter
reporters: ['progress'],
// web server port
port: 9876,
// enable / disable colors in the output (reporters and logs)
colors: true,
// level of logging
// possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
logLevel: config.LOG_INFO,
// enable / disable watching file and executing tests whenever any file changes
autoWatch: false,
// start these browsers
// available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
browsers: ['Chrome'],
// Continuous Integration mode
// if true, Karma captures browsers, runs the tests and exits
singleRun: true
});
};
実行方法
プロジェクトのルートディレクトリに移動し、コマンドラインから下記を実行してください。
$ grunt
gruntコマンドを引数なしで実行しているため、Gruntfile.jsで定義しているデフォルトのタスク(『grunt.registerTask("default", "esteWatch");』)が実行されます。
esteWatchはテスト対象のコード(js/index.js)およびテストコード(test/index_test.js)の変更を監視しています。
これらのファイルに変更があれば、前述の処理の流れでテストを実行します。
実行結果
ファイル変更・保存時→テスト失敗
テスト失敗時の例として、こういったサンプルにありがちな、Greetingオブジェクトのgreet関数のテストを実行してみたいと思います。
テスト対象のコード(js/index.js)
window.ns = {};
(function (ns) {
'use strict';
var Greeting = ns.Greeting = function (name) {
this.name = name;
};
Greeting.prototype.greet = function () {
return 'Hello, ' + this.name + '!!';
};
}(ns));
テストコード(test/index_test.js)
describe('Greeting', function () {
describe('#greet()', function () {
it('Greetingをnewする時にnameを渡さなかった場合', function () {
var greeting = new ns.Greeting();
assert(greeting.greet() === 'Hello, unknown!!');
});
});
});
test/index_test.jsか、js/index.jsを変更・保存すると、下記の通りテストが実行されます。
Running "esteWatch" task
>> Waiting...
>> User action.
>> File changed: test/index_test.js
Running "espower:test" (espower) task
Running "karma:unit" (karma) task
INFO [karma]: Karma v0.12.19 server started at http://localhost:9876/
INFO [launcher]: Starting browser Chrome
INFO [Chrome 36.0.1985 (Mac OS X 10.9.2)]: Connected on socket hklYkb1RNy6QzmNzoXL1 with id 78195425
Chrome 36.0.1985 (Mac OS X 10.9.2) Greeting #greet() Greetingをnewする時にnameを渡さなかった場合 FAILED
# test/index_test.js:5
assert(greeting.greet() === 'Hello, unknown!!')
| | |
| | false
| "Hello, undefined!!"
Object{name:undefined}
--- [string] 'Hello, unknown!!'
+++ [string] greeting.greet()
@@ -6,11 +6,13 @@
, un
-known
+defined
!!
ファイル変更・保存時→テスト成功
テストが通るようにテスト対象のコード(js/index.js)を変更・保存すると、下記の通りテストが実行されます。
Running "esteWatch" task
>> Waiting...
>> User action.
>> File changed: test/index_test.js
Running "espower:test" (espower) task
Running "karma:unit" (karma) task
INFO [karma]: Karma v0.12.19 server started at http://localhost:9876/
INFO [launcher]: Starting browser Chrome
INFO [Chrome 36.0.1985 (Mac OS X 10.9.2)]: Connected on socket b1JTt49h-waXPjMA6Jb2 with id 87727203
Chrome 36.0.1985 (Mac OS X 10.9.2): Executed 3 of 3 SUCCESS (0.129 secs / 0.003 secs)
Running "esteWatch" task
>> Waiting...
これでテストがさくさく書けますね!