jasmine-node で JavaScript のロジックのテストをする
JS でもテストしたい
DOM や非同期はおいといて、ロジックくらいテストしたい。Sinon.js とかモックしたり、非同期処理のテストのライブラリがあったり、それこそ古くからは Selenium とかあるけど、いまはそこまでしなくていいから JS でテストをするってのをやりたかった。で、いろいろあるけど Jasmine がとりあえず楽らしいし、コンソールでやりたいので jasmine-node を利用した。
JavaScriptテクニックバイブル ~効率的な開発に役立つ150の技
- 作者: JSサポーターズ
- 出版社/メーカー: 技術評論社
- 発売日: 2012/08/31
- メディア: 単行本(ソフトカバー)
- 購入: 38人 クリック: 1,794回
- この商品を含むブログ (11件) を見る
jasmine-node の導入と簡単なテスト
基本的にはこれを参考にした
jasmine-nodeを使ってテスト - デラエモン、カイハツニッキ
てきとうにディレクトリを掘って作業する。
# てきとうにつうる mkdir jasmine cd jasmine # lib でもいいかも mkdir src # テストケース、spec mkdir spec # 導入 npm install jasmine-node
src 以下に元となるコード、spec 以下にテストケースをかく。spec のことはあまりしらないけど、unittest だと class に HogeTest(unittest.TestCase) っていれたり def test_hoge(self) するように、どうやら Spec では MySpec(mixedCase)か my_spec(lower_case) にするらしい。関係なくマッチさせるには --matchall ってオプションをつければ良い。あと --verbose で詳細がでるとかはまあ --help みればよい。
/path/to/node_modules/jasmine-node/bin/jasmine-node ./spec/ --autotest --color
よい
requirejs で依存している他のモジュールも一括でテストしたい
@__10100__ さんの を参考にした jasmine-node とRequireJS - 断章10100
この記事だと src 以下に config.js を置いているけど、自分はプロジェクトルートにおいておいた。config.js はこんなかんじでほぼ同じ。
var requirejs = require("requirejs"); requirejs.config({ // node のモジュールはそのまま nodeRequire: require, // spec 実行時に __dirname が spec のパスにかわるため // ちゃんとやるなら require('path') で絶対パス求めたほうがいいかも baseUrl: __dirname + '/../src' });
requirejs の config については以下も参考にした。
がぶっとひとかみ
shim で記述できないか試したけどちょっとうまくいかなかった。まあ、後述の方法で対処した。
RequireJS の内容を踏まえて同時にファイル監視もついでにまわしてやることにした
/path/to/node_modules/jasmine-node/bin/jasmine-node --runWithRequireJs --requireJsSetup config.js ./spec/ --autotest --color
なので、いままでオレオレで習作してたコードを試したりもした。
src/a.js の文字列のやつなら
String.prototype.format = function (arg) { // @param {String} or {Object} // this = String var tmp = this; // Object if (typeof arg === "object"){ for (var elem in arg){ tmp = tmp.replace("{" + elem + "}", arg[elem]); } // String } else { for (var i=0; i < arguments.length; i++) { tmp = tmp.replace("{" + i + "}", arguments[i]); } } return tmp; };
spec/a_spec.js でたとえばこんな感じ。
requirejs(['a'], function (a) { describe("おれおれフォーマッター", function () { var str_obj = null; var str_ary = null; beforeEach(function () { str_obj = new a.String("abc {hoge} {moge}"); str_ary = new a.String("abc {0} {1}"); }); it("自作 String.format 1", function () { describe("object のとき", function () { // なにもしない場合 expect(str_obj).toEqual("abc {hoge} {moge}"); // 引数を入れる object expect(str_obj.format({hoge: "text"})).toEqual("abc text {moge}"); // 引数を複数入れる object expect(str_obj.format({hoge: "text1", moge: "text2"})).toEqual("abc text1 text2"); }); describe("ary っぽくするとき", function () { // なにもしない場合 expect(str_ary).toEqual("abc {0} {1}"); // 引数を入れる array int expect(str_ary.format(1)).toEqual("abc 1 {1}"); // 引数を入れる array string expect(str_ary.format("baba")).toEqual("abc baba {1}"); // 引数を複数入れる array int expect(str_ary.format(4,5)).toEqual("abc 4 5"); // 引数を複数入れる array string expect(str_ary.format("moha", "toge")).toEqual("abc moha toge"); // 引数を複数入れる array string int expect(str_ary.format("moha", 4)).toEqual("abc moha 4"); }); }); }); });
いいですね。あとはゴリゴリかいていくだけです。実際いくつかコケて修正しました。
ついでに - CommonJS, AMD について
RequireJS って単純に依存関係を管理したいだけだと思ってたんだけど、実際はもうちょっとあって、非同期読み込みとモジュール化による名前空間の提供が目的らしい。もうちょっと言えば RequireJS 以前の提案があったらしいが、実装としていまは RequireJS が使える、という話。
CommonJS は Node とも関係があって、歴史経緯的にクライアントサイドで使われてたけど、サーバーサイドでいろいろやるならこの際モジュール管理とかそういうのしないとだめだよねみたいな話がある。で、それの標準化の提案として CommonJS がある。このときに define と require という予約語っぽいのを決めてこう使いましょうという提案がある。で、AMD はもうちょっと発展させて CommonJS を発展させて非同期読み込みを実現するための API という感じらしい。
具体的な設定としては参考資料をみながらやった。元のスクリプトが AMD どころか Node のモジュールでもないただの JS だったので、こんな感じにした。
(function(define) { // id, deps, factory(func or obj) // id は省略、依存は特にないので空、モジュールを返すために factory とつける define([], function () { // てきとうな名前空間をつくってやる var _module = {}; // てきとうな関数を用意してやる function pf() { var tmp = arguments[0]; for (var i=1; i < arguments.length; i++) { tmp = tmp.replace(/%s/, arguments[i]); } return tmp; } // モジュールのプロパティに生やしてやる _module.pf = pf; // で、最終的にモジュール自体を返してやる return _module; }); }) ( typeof define !== 'undefined' ? // AMD 対応していて define がつかえるならそのまま AMD としてつかう define : typeof module !== 'undefined' ? // If no define, look for module to export as a CommonJS module. // で、 module があるのなら Node と判断して module.exports に生やしてやる。 function(deps, factory) { module.exports = factory(); } : // 最終的にどうもブラウザっぽいならそっちに生やす // つまり window.a としてグローバルに生やす // for this === window function(deps, factory) { this['a'] = factory(); } );
最近のJavaScriptモジュールの書き方 - yo_waka's blog
Dojo道場 ~ 第11回「Dojo 最新動向 - Asynchronous Module Definition」 (1/5):CodeZine
パーフェクトJavaScript (PERFECT SERIES 4)
- 作者: 井上誠一郎,土江拓郎,浜辺将太
- 出版社/メーカー: 技術評論社
- 発売日: 2011/09/23
- メディア: 大型本
- 購入: 24人 クリック: 586回
- この商品を含むブログ (12件) を見る
まあ
少なくてもロジックに対してテストができるというのは安心感がある。