Knockout.jsとKarmaで簡単TDD開発

この前の関ジャバの懇親会で @hakurai さんに良さを教えてもらってから、Knockout.jsを始めてみました。


Knockout.jsを使うことにした理由は以下の通り。


・プロジェクトの途中から使っても比較的簡単に導入できる
 →Knockout.jsはAngularJSなどと比べてRouterなどの機能がなく非常にシンプル

・わかりやすい
 →バインディングすることに機能が絞られているので学習コストは少ないと思う

・テストコードが書きやすい
 →これはKnockout.jsに限ったことではないけど、ViewModelをJSファイルに分離することによりテストが書きやすくなると思う



んで、JSのテストするのに色々調べてたらKarma使うと色々便利っぽかったので試してみると思った以上に良かったので、セットアップ方法を書いてみようと思う。



セットアップの流れ

以下のような流れでセットアップした。

ちなみにインストールは npm 使うので、無い場合は先にインストールしなければいけないです。
(このへんguardとかsass使うときはgem使わないとインストールできないし、どっちも入れないと動かないとか色々面倒なので、なんとかならんのかなぁと思ってる)

  1. Gruntをインストール
  2. package.jsonとGruntfileを用意
  3. Karmaをインストール
  4. karma.conf.jsを設定

 

  • Gruntをインストール

GruntはJSとかCSSとかのminifyしたりだとか色々できるツール
自分でタスクつくったりできるし Ant っぽい感じだと思ってる。

インストールは npm で。

npm install -g grunt-cli
  • package.jsonとGruntfileを用意

package.json

{
	"name": "ko-karma-sample",
	"version": "0.0.0",
	"description": "A sample project of KnockoutJS + Karma",
	"devDependencies": {
		"grunt": "~0.4.1",
		"grunt-karma": "~0.7.1",
		"karma": "~0.10",
		"karma-junit-reporter": "~0.1",
		"karma-growl-reporter": "~0.1",
		"karma-coverage": "0.1.0"
	}
}

Gruntfile (とりあえずKarmaだけ)

module.exports = function(grunt) {

  // Project configuration.
  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),
    karma: {
      unit: {
        configFile: 'karma.conf.js',
        autoWatch: true
      }
    }
  });
  grunt.loadNpmTasks('grunt-karma');

};

とりあえずここまで書いたら、以下のコマンドを叩く。

npm install --save-dev

すると、node_modulesというディレクトリができて、それ以下はこんな感じになる
f:id:tan_go238:20131110004702p:plain


  • Karmaをインストール

さっきの npm install時に入っちゃってるけど、-g 入れてどこでも使えるようにしとく。

npm install -g karma
  • karma.conf.jsを設定

Karmaの設定はこんな感じ。プロジェクトのディレクトリ構成とか違う場合は調整が必要。

module.exports = function (config) {
	config.set({

		// base path, that will be used to resolve files and exclude
		basePath: '',

		frameworks: ['jasmine'],
		files: [
			'app/js/jquery-1.10.2.min.js',
			'app/js/knockout-3.0.0.js',
			'app/js/page/**/*.js',
			'test/page/**/*.js'
		],
		exclude: [
			'**/*.swp'
		],

		// test results reporter to use
		// possible values: 'dots', 'progress', 'junit', 'growl', 'coverage'
		//reporters: ['progress', 'junit', 'coverage'],
		reporters: ['progress', 'growl'],

		junitReporter: {
		outputFile: 'reports/junit/test-results.xml',
		suite: ''
		},

		preprocessors: {
			'app/js/page/**/*.js': ['coverage']
		},
		coverageReporter: {
			type: 'cobertura',
			dir: 'reports/coverage/',
			file: 'coverage.xml'
		},
		port: 9876,
		colors: true,
		logLevel: config.LOG_INFO,
		autoWatch: true,
		captureTimeout: 60000,
		singleRun: false

		// Start these browsers, currently available:
		// - Chrome
		// - ChromeCanary
		// - Firefox
		// - Opera
		// - Safari (only Mac)
		// - PhantomJS
		// - IE (only Windows)
		browsers: ['PhantomJS'],
	});
};


以上で設定はできたのでテスト書いてみる。


テストを書いてみる

  1. karma-watchとkarma-growlを使って変更を監視しつつ自動的にテストする

以下のコマンドを叩いてKarmaを起動。autowatch を true に設定しているので変更があれば自動的にテストを実行する

grunt karma:unit

もしくは

grunt karma

でOK。

  1. テストを追加する

サンプルなので簡単な感じで。
書き方はjasmineを参考に。

test/page/indexTest.js

describe("足し算だっ!", function(){
	it("1と2を足すと3、だよね?", function(){
		var vm = new ViewModel();
		vm.value1("1");
		vm.value2("2");
		expect(vm.result()).toEqual(3);
	});
});

テスト書いただけではまだ実装がないので、当然エラー。
テスト保存時にgrowlが通知してくれると思います。
※growlは個人的に購入してるので無い人はどうすれば良いかわかりません。おしえてください。

f:id:tan_go238:20131110013620p:plain

  1. テストに対応したJSを書く
function ViewModel() {
	var self = this;
	
	self.value1 = ko.observable(0);
	
	self.value2 = ko.observable(0);
	
	self.result = ko.computed(function(){
		return parseInt(self.value1()) + parseInt(self.value2());
	});
}


保存したらテストが走ってテストが成功したと思います。
f:id:tan_go238:20131110013637p:plain

  1. 最後に確認のためにHTMLも書く
<!DOCTYPE html>
<html>
<head>
    <title>A sample project of Knockout.js + Karma</title>
</head>
<body>
<input type="text" data-bind="value: value1"/>

&nbsp;+ &nbsp;

<input type="text" data-bind="value: value2"/> 

&nbsp; = <span data-bind="text: result"></span>

<script src="js/jquery-1.10.2.min.js"></script>
<script src="js/knockout-3.0.0.js"></script>
<script src="js/page/index.js"></script>
<script>
$(function(){
ko.applyBindings(new ViewModel());
});
</script>
</body>
</html>

ブラウザでプレビューするとこんな感じ。
f:id:tan_go238:20131110021816p:plain



いじょ。
コードはGistに上げてます。

ちなみに余談ですが、junitとcoverageの設定もしてるので、karma.conf.js の reporters に
'junit' と 'converage' を入れて、singleRun で動かすとJenkinsなどのCIサーバでも動かせるかと思います。
ただKnockout.jsだと自動バインディングでobservableに入ると勝手にcomputedも実行しちゃったりするからカバレッジは微妙かも。


Gistにあげたコード。ディレクトリは「_」で区切ってます。
※今回のサンプルプロジェクトのフォルダ構成はこんな感じです。
f:id:tan_go238:20131110023528p:plain

https://gist.github.com/tango238/7387690A sample project of Knockout.js and Karma