ブログ引っ越しました
ブログ引っ越しました。
http://blog.kazupon.jp
従来の記事は、移動がメンドイのでこのままにしておきます。
vows に追加されているアンドキュメントな機能
JavaScript Advent Calendar 2011 (Node.js/WebSocketsコース) 12日目の記事です。
Node.js におけるテストフレームである、vows について書いてみました。
はじめに
この記事では、README.md や 本家 Web サイトのドキュメントに載っていないことを主に書いています。
最新バージョンである 0.6.0 の vows を元に、おさらいした後、以下の3つについて説明します。
- assert
- teardown
- sub-events
0. vows についておさらい
vows は、Node.js 向けの RSpec のような BDD できるフレームワーク です。
vows は非同期なコードをテストするために作られたフレームワークとなっています。
インストール
vows は npm を使って下記コマンドでインストールできます。
$ npm install -g vows
テストの基本構造
vows では、基本、以下に示したような構造でテストを書きます。
var vows = require('vows'); var assert = require('assert'); var suite = vows.describe('Array'); suite.addBatch({ // batch 'An array with 3 elements': { // context topic: [1, 2, 3], // topic 'has a length of 3': function (topic) { // vow assert.equal(topic.length, 3); } }, 'An array': { // context 'with zero elements': { // sub-context topic: [], // topic 'has a length of 0': function (topic) { // vow assert.equal(topic.length, 0); }, 'returns *undefined*, when `pop()`ed': function (topic) { // vow assert.isUndefined(topic.pop()); } } } }).export(module);
まず、vows.describe で テストスイートの説明である subject を指定して suite を作ります。上記のコードでは、'Array' を指定して suite を作っています。suite は JUnit でいう複数のテストをまとめたものです。
次に、suite.addBatch で batch を suite に追加します。batch はテストの単位で、複数の context で構成することができます。上記のコードでは、2つの context を持つ batch として追加しています。suite の addBatch は batch を追加すると、その suite 自身返します。
context は対象となるテストの説明を表したものです。上記のコードのように、context とは 1つの topic と 複数の vow を持つことができます。また、上記のように、context に context を記述してネストすることもできます。
topic は context におけるテストの対象です。topic では、上記のように、直接値を記述したり、振舞いを記述した関数を記述することができます。topic で関数を記述した場合、context の中に複数の vow があったとしても評価されるのは、該当 context が実行されたとき一度だけです。
vow は topic がどうなるべきか記述された明言です。上記のように、vow は引数に topic を受け取る関数として記述し、関数内では、assert で topic をチェックします。
最後に、suite の export で module を指定することで、ファイルに記載されたテストが vows で実行できるよう公開します。
書いたテストの実行
上記コードをテストするには、以下のコマンドを実行します。
$ vows basic.js
実行結果としては、以下のように出力されます。
'--spec' オプションを指定してコマンドを実行すると以下のようになります。
context の説明と vow の説明、そしてテスト結果が、一覧で出力されます。テストの出力結果がカラーづけされているので、どこがテスト通らなかったか、分かりやすいです。
非同期イベントのテスト
Node.js では、非同期イベントをバシバシ使うので、非同期イベントのテストを書く必要があります。vows で非同期イベントをテストするには、
- topic の this.callback 関数
- EventEmitter インスタンスによるプロミス
の2種類の方法があります。以下に、上記2種類の方法で書いたテストを示します。
var vows = require('vows'); var assert = require('assert'); var EventEmitter = require('events').EventEmitter; var fs = require('fs'); vows.describe('async').addBatch({ 'this.callback : ': { 'hoge.txt': { topic: function () { fs.stat('./hoge.txt', this.callback); }, 'can be accessed': function (err, stats) { assert.isNull(err); assert.isObject(stats); }, 'is not empty': function (err, stats) { assert.isNotZero(stats.size); } } }, 'EventEmitter : ': { 'hoge.txt': { topic: function () { var promise = new EventEmitter(); fs.stat('./hoge.txt', function (err, stats) { if (err) { promise.emit('error', err); } else { promise.emit('success', stats); } }); return promise; }, 'can be accessed': function (err, stats) { assert.isNull(err); assert.isObject(stats); }, 'is not empty': function (err, stats) { assert.isNotZero(stats.size); } } } }).export(module);
this.callback で非同期イベントをテストするには、topic の関数内で対象となる非同期イベントのコールバックの部分に this.callback を与えてやるだけです。これで、vows が その topic の中で同じ context の vow を呼び出すようになります。this.callback による vow の呼び出しした際のパラメータの形式は、 function (err, arg1, arg2 ...) の形式になります。
EventEmitter によるプロミスの場合は、topic の関数内で EventEmitter のインスタンスを作って、topic の関数は、その作ったインスタンスを戻り値として返す必要があります。同じ topic の関数内で、作成したインスタンスが対象となる非同期イベントのコールバック内で、'success' イベントを生成するようにしておくことで、vows が topic と同じ context の vow を呼び出すようになります。'success' イベントは、promise.emit('success', arg1, arg2 ...); のように指定することで、vow でパラメータを受け取ることができます。非同期イベントのエラーの場合は、'error' イベントを生成するようにします。'error' イベントの場合も promise.emit('error', err, arg1 ...); のように指定すると、vow でパラメータを受け取ることができます。
以上が、vows についておさらいです。
1. assert
vows で提供する assert は Node.js で提供している assert を拡張しています。その assert の中で、一部ドキュメントの reference に記載されていないものがあります。reference の一覧にないものを順に以降挙げて説明します。
assert.greater(actual, expected, [message])
actual が expected より大きいかどうかチェックします。
条件が合致しない場合は、message を出力します。
assert.greater(5, 4);
assert.lesser(actual, expected, [message])
actual が expected より小さいかどうかチェックします。
条件が合致しない場合は、message を出力します。
assert.lesser(4, 5);
assert.inDeleta(actual, expected, delta, [message])
actual が expected - delta 〜 expected + delta の範囲内であるかどうかチェックします。
(数式で表現すると、expected - delta < actual < expected + delta)
条件が合致しない場合は、message を出力します。
assert.inDelta(42, 40, 5); assert.inDelta(42, 40, 2); assert.inDelta(42, 42, 0); assert.inDelta(3.1, 3.0, 0.2);
assert.lengthOf(actual, expected, [message])
actual の長さが expected であるかどうかチェックします。
条件が合致しない場合は、message を出力します。
assert.lengthOf([0, 1], 2);
昔から vows を使っている人は、assert.length を使っているかと思いますが、assert.length は今後、assert.lengthOf にとって代わるので、今後はこちらの assert.lengthOf を使う必要があります。
(軽く調べた感じですと、0.5.12 から assert.lengthOf 追加されているようです。)
assert.isNotEmpty(actual, [message])
actual が 空かどうかチェックします。
条件が合致しない場合は、message を出力します。
assert.isNotEmpty({ hoge: 4 });
assert.isDefined(actual, [message])
actual が undefined でないかどうかチェックします。
条件が合致しない場合は、message を出力します。
assert.isDefined(null); assert.isDefined(1); assert.isDefined({});
assert.isZero(actual, [message])
actual が 0 かどうかチェックします。
条件が合致しない場合は、message を出力します。
assert.isZero(0);
assert.isNotZero(actual, [message])
actual が 0 でないかどうかチェックします。
条件が合致しない場合は、message を出力します。
assert.isNotZero(1);
assert.includes(actual, expected, [message])
actual に expected が含まれているかどうかチェックします。
assert.includes は assert.include のエイリアスですので、assert.include と同じです。
条件が合致しない場合は、message を出力します。
assert.includes([0, 4, 33], 4); assert.includes('hello world', 'hello'); assert.includes({ foo: 4 }, 'foo');
assert.deepInclude(actual, expected, [message])
actual に expected が含まれているかどうか、深い同値性でチェックします。
条件が合致しない場合は、message を出力します。
assert.deepInclude([0, 4, 33], 4); assert.deepInclude([{ foo: 4 }, { bar: 33 }], { foo: 4 }); assert.deepInclude('hello world', 'hello'); assert.deepInclude({ foo: 4 }, 'foo');
内部の動作として、github のソースを見た感じですと、actual が配列であった場合は、各要素を assert.deepEqual で actual に対して expected であるかどうかチェックしているようです。actual が配列以外の場合は、assert.include でチェックしているようです。
1. teardown
vows でも、JUnit などのフレームワークのように、後処理を行う teardown をサポートしています。
使い方
teardown は、context の中に書きます。コードで書くと実際こんな感じになります。
var vows = require('vows'); var assert = require('assert'); vows.describe('teardown').addBatch({ 'context': { topic: function () { // do something ... return { flag: true }; }, 'vow1': function (topic) { assert.isTrue(topic.flag); }, 'vow2': function (topic) { assert.isTrue(topic.flag); }, teardown: function (topic) { console.log('context teardown !!'); topic.flag = false; } }, }).export(module);
上記コードには、動作が分かりやすくするため、console.log 入れてます。teardown は batch の 全てcontext の中の vow が全て実行された後に実行されます。この動作は、RSpec でいう after(:all) に近い感じです。上記コードを実行すると以下のようになります。
$ vows teardonw0.js --spec ♢ teardown context ✓ vow1 ✓ vow2 context teardown !! ✓ OK » 2 honored (0.004s)
ネストした context にも書ける
teardown はネストした context 、つまり、subcontext 中にも書くことができます。また、subcontext の親の context にも teardown を書くこともできます。上記コードを少し改造したコードを以下に示します。
var vows = require('vows'); var assert = require('assert'); vows.describe('teardown').addBatch({ 'context': { topic: function () { // do something ... return { flag: true }; }, 'vow1': function (topic) { assert.isTrue(topic.flag); }, 'vow2': function (topic) { assert.isTrue(topic.flag); }, teardown: function (topic) { console.log('context teardown !!'); topic.flag = false; }, 'subcontext': { 'nested vow': function (topic) { assert.isTrue(topic.flag); }, teardown: function (topic) { console.log('subcontext teardown !!'); } }, }, }).export(module);
上記コードを実行すると以下の結果になります。
$ vows teardown1.js --spec ♢ teardown context ✓ vow1 ✓ vow2 context subcontext ✓ nested vow subcontext teardown !! context teardown !! ✓ OK » 3 honored (0.004s)
上記結果より、subcontext の teardown と、 subcontext の 親の context の teardown が実行されていることが分かります。上記コードのように、複数の teardown が batch 内にある場合は、github のソースを見た感じですと、最後に実行した context の teardown から舐めるよう実行するようです。
また、context は非同期で実行されるため、teardown の実行順序は毎回記述した順序で発生するとは限りません。このため、他の teardown に依存するようなテストのコーディングは避けた方がよいでしょう。
teardown で非同期な処理に対応する
teardown は後始末をするには最適なので、socket のサーバの close 処理のような非同期イベントを発生させる処理を書くことがあるかと思います。
teardown は、batch 内にある context が全て完了してから開始し、すべてのteardown が実行されたときに、次に batch に進みます。
もし、teardown に非同期な処理がある場合は、どうなるんでしょうか。では、以下のコードでその動作を確認してみましょう。
var vows = require('vows'); var assert = require('assert'); var flag = false; vows.describe('teardown').addBatch({ 'context1': { topic: function () { // do something ... flag = true; return true; }, 'vow1': function () { assert.isTrue(flag); }, teardown: function (topic) { var asyncDoSomething = function () { setTimeout(function () { console.log('teardown !!'); flag = false; }, 10); }; asyncDoSomething(); } } }).addBatch({ 'context2': { 'vow2': function () { assert.isFalse(flag); } } }).export(module);
上記コードの context1 の teardown 内の asyncDoSomething 関数は、非同期処理をエミュレートした関数です。この関数の中では、10 ms 経過すると、console.log でメッセージを出力し、flag を false で初期化します。
context2 の vow2 では、flag が false であるかどうかをチェックしています。
上記コードを実行すると以下のようになります。
$ vows teardown2.js ♢ teardown context1 ✓ vow1 context2 ✗ vow2 » expected false, got true // teardown2.js:29 ✗ Broken » 1 honored ∙ 1 broken (0.018s)
テストが失敗してしまいました。。。
asyncDoSomething 関数での console.log の内容は出力されていません。このことから、teardown に非同期処理に構わず、次の batch に進んでしまっていることが分かったかと思います。
socket の close ような、非同期イベントでリソース解放の完了通知を受け取るような非同期処理がある場合は、これでは都合が悪いです。
実は、これに対応する方法はあります。それは、vows で非同期イベントのテストと同じく this.callback を利用します。以下は上記コードを少し改造した、teardown での this.callback の使い方のコードです。
var vows = require('vows'); var assert = require('assert'); var flag = false; vows.describe('teardown').addBatch({ 'context1': { topic: function () { // do something ... flag = true; return true; }, 'vow1': function () { assert.isTrue(flag); }, teardown: function (topic) { var asyncDoSomething = function (cb) { setTimeout(function () { console.log('teardown !!'); flag = false; cb(); }, 10); }; asyncDoSomething(this.callback); } } }).addBatch({ 'context2': { 'vow2': function () { assert.isFalse(flag); } } }).export(module);
このコードでは、teardown で、asyncDoSomething 関数にコールバック関数として this.callback を指定しています。
そして、asyncDoSomething 関数では、パラメータで指定されたコールバック関数が実行されるようにしています。
上記のコードを実行すると以下の結果になります。
$ vows teardown3.js --spec ♢ teardown context1 ✓ vow1 teardown !! context2 ✓ vow2 ✓ OK » 2 honored (0.016s)
テストが通りました!
10ms 経過して、console.log が出力しているので、きちんと非同期処理に対応できたということになります。このことから、teardown で非同期な処理をしたい場合は、this.callback を利用すると幸せになるでしょう。
3. sub-events
バージョン0.6.0では、sub-events という機能がサポートされました。
sub-events により以下のようなことができるようになります。
- topic 内の処理で発生した複数の非同期イベントをテストすることができます
- 非同期イベントの発生順もテストすることができます
では、具体的にコードでどう使うのか説明していきましょう。
カウントダウンタイマーについて
まずは、以降のコードで利用する、1秒間隔でカウントダウンするタイマーのコードを以下に載せておきます。
var EventEmitter = require('events').EventEmitter; var createTimer = function () { var timer = Object.create(EventEmitter.prototype, { run: { value: function (sec, cb) { var self = this; var count = sec; var id = setInterval(function () { if (count === 0) { clearInterval(id); return; } if (count <= 3) { self.emit(count.toString(), count); } count--; }, 1000); cb && cb(); } } }); return timer; }; exports.createTimer = createTimer;
createTimer 関数は、カウントダウンタイマーを作成する関数です。この関数で作成されたカウントダウンタイマーは、run メソッドでタイマーを開始します。
run メソッドにはカウントダウンを開始秒数であるパラメータ sec と、カウントダウンが開始された際に呼び出されるコールバック関数をパラメータ callback に指定します。
カウントダウンタイマーは、カウントダウンを開始してから、3秒前に、'3' イベント、2秒前に '2' イベント、1秒前に '1' イベントを生成します。各イベントのパラメータ ret には、それぞれのイベントのカウントダウン値('2'イベントだったら、2)が渡ってくるという仕様です。
run メソッドとこれらイベントの実装内容については、ここでは本質的な内容ではないので説明割愛します。
sub-events の使い方
上記カウントダウンタイマーを利用した、sub-events のコードの例を以下に示します。
var vows = require('vows'); var assert = require('assert'); var EventEmitter = require('events').EventEmitter; var createTimer = require('./timer').createTimer; vows.describe('sub-events').addBatch({ 'calling `run` with 5 sec': { topic: function () { var promise = new EventEmitter(); var timer = createTimer(); timer.on('3', function (ret) { promise.emit('three', ret); }); timer.on('2', function (ret) { promise.emit('two', ret); }); timer.on('1', function (ret) { promise.emit('one', ret); }); timer.run(5, function () { promise.emit('success', timer); }); return promise; }, on: { 'three': { 'will emit `3` value': function (ret) { assert.equal(ret, 3); } }, 'two': { 'will emit `2` value': function (ret) { assert.equal(ret, 2); } }, 'one': { 'will emit `1` value': function (ret) { assert.equal(ret, 1); } } } } }).export(module);
この例では、カウントダウンを5秒前で開始して run によって発生する各非同期イベントのテストをするというものになっています。
部分的にコードを示しながら順に内部を詳しく見て行きましょう。
topic での振る舞いの定義
'calling `run` with 5 sec': { topic: function () { var promise = new EventEmitter(); var timer = createTimer(); timer.on('3', function (ret) { promise.emit('three', ret); }); timer.on('2', function (ret) { promise.emit('two', ret); }); timer.on('1', function (ret) { promise.emit('one', ret); }); timer.run(5, function () { promise.emit('success', timer); }); return promise; }, }
topic では、プロミス (promise) とカウントダウンタイマー (timer) を作成しています。sub-events においても、非同期イベントのテストでプロミスを利用するのは従来どおりです。
続いて、カウントダウンタイマーの非同期な'3'、'2'、'1' イベントをハンドリングしています。それぞれの非同期イベントのハンドリング処理としては、プロミスで各非同期イベントに対応するイベント名が生成されるよう、取得したパラメータ ret を指定して emit しています。具体的に'3'イベントの場合ですと、プロミスで emit でイベント名 'three'、パラメータに '3'イベントで取得した ret をそのまま emit でパラメータとして渡してイベントを生成するようにいった感じです。
カウントダウンタイマーの run メソッドでは、 5秒前からカウントダウンし、カウントダウンが開始された際にコールバックするようしています。run メソッドのコールバック内の処理では、先程作成したプロミスの emit で 'success' イベントを生成するにしています。sub-events において、topic 内の非同期イベントの振舞いをテストするには、プロミスによる 'success' イベントを生成するよう書く必要があります。
topic の最後では、プロミスを返すようにします。
非同期イベントのハンドリング
on: { 'three': { 'will emit `3` value': function (ret) { assert.equal(ret, 3); } }, 'two': { 'will emit `2` value': function (ret) { assert.equal(ret, 2); } }, 'one': { 'will emit `1` value': function (ret) { assert.equal(ret, 1); } } }
topic 内で発生する非同期イベントをハンドリングするには、on を利用します。on には、topic 内で発生するイベント名のプロパティで、vow を持つ context として設定します。ここで指定する vow は従来どおりfunction(arg1, arg2 ...) のような promise.emit で指定されたパラメータを受け取るコールバック形式で指定します。
上記のコードでは、topic 内のプロミスによって発生する 'three'、'two'、そして'one'イベントをハンドリングするため、'three'、'two'、'one'の context を on に設定しています。それぞれの context の vow では、パラメータとして、ret をうけとって、assert.equal でテストするようにしています。
実行結果
このサンプルを実行してみましょう。
$ vows subevents1.js --sepc ♢ sub-events calling run with 5 sec on three ✓ will emit 3 value calling run with 5 sec on two ✓ will emit 2 value calling run with 5 sec on one ✓ will emit 1 value ✓ OK » 3 honored (5.003s)
カウントダウンタイマーにより、3秒、2秒、1秒と経過する順に、テストが実行されて、最終的には上記のような結果が出力されるはずです。
on の description の部分は、'on xxxx' という風な感じで vows 側で出力してくれます。
sub-events を使わないでテストコードを書いた場合
sub-events を使わない、従来の方法で、このケースをテストした場合は、
多分、下記のようなコードになるでしょう。
var vows = require('vows'); var assert = require('assert'); var EventEmitter = require('events').EventEmitter; var createTimer = require('./timer').createTimer; vows.describe('sub-events-no-use').addBatch({ 'calling `run` with 5 sec': { topic: function () { var promise = new EventEmitter(); var timer = createTimer(); this.timer = timer; timer.run(5, function () { promise.emit('success', true); }); return promise; }, 'on `three` event': { topic: function () { var promise = new EventEmitter(); var timer = this.timer; timer.on('3', function (ret) { promise.emit('success', ret); }); return promise; }, 'will emit `3` value': function (topic) { assert.equal(topic, 3); }, 'on `two` event': { topic: function () { var promise = new EventEmitter(); var timer = this.timer; timer.on('2', function (ret) { promise.emit('success', ret); }); return promise; }, 'will emit `2` value': function (topic) { assert.equal(topic, 2); }, 'on `one` event': { topic: function () { var promise = new EventEmitter(); var timer = this.timer; timer.on('1', function (ret) { promise.emit('success', ret); }); return promise; }, 'will emit `1` value': function (topic) { assert.equal(topic, 1); } } } } } }).export(module);
通常の方法では、topic では、'success' か 'error' かの1つしか非同期イベントをテストできません。vows でいう macro でも書かなきゃ、上記のような非同期イベントごとに七面倒臭く context を作ってどんどん右にネストしていくのでメンテナンスしづらいテストコードになるのが普通ではないんでしょうか。sub-events を使ったほうが断然キレイなメンテナンスしやいテストコードであることは間違いないでしょう。
非同期イベントの発生順をテストする
sub-events では非同期イベントの発生順も容易にテストすることができます。sub-events の説明で利用したコードを発生順に対応させたコードと実行結果を以下に示します。
var vows = require('vows'); var assert = require('assert'); var EventEmitter = require('events').EventEmitter; var createTimer = require('./timer').createTimer; vows.describe('sub-events-order').addBatch({ 'calling `run` with 5 sec': { topic: function () { var promise = new EventEmitter(); var timer = createTimer(); timer.on('3', function (ret) { promise.emit('three', ret); }); timer.on('2', function (ret) { promise.emit('two', ret); }); timer.on('1', function (ret) { promise.emit('one', ret); }); timer.run(5, function () { promise.emit('success', timer); }); return promise; }, on: { 'three': { 'will emit `3` value': function (ret) { assert.equal(ret, 3); }, on: { 'two': { 'will emit `2` value': function (ret) { assert.equal(ret, 2); }, on: { 'one': { 'will emit `1` value': function (ret) { assert.equal(ret, 1); } } } } } } } } }).export(module);
$ vows subevents2.js --spec ♢ sub-events-order calling run with 5 sec on three ✓ will emit 3 value calling run with 5 sec on three on two ✓ will emit 2 value calling run with 5 sec on three on two on one ✓ will emit 1 value ✓ OK » 3 honored (5.005s)
イベントの発生順は、上記コードのように、event に対して on を設定して event をネストして記載していきます。ネストされた event は、上位の event よりも後にイベントが発生しないといけないということを意味します。上記コードでは、イベントが 'three' 、'two' 、'one' という順でネストしていますが、'two' は 'three' よりも後に、'one' は 'two' とりも後に、イベントが発生しないといけないということになります。
イベント発生順序のテストに失敗した場合は、どうなるか。上記コードの 'one' イベントと 'two' イベントを入れ替えみて実行させると以下のような、結果になるはずです。
var vows = require('vows'); var assert = require('assert'); var EventEmitter = require('events').EventEmitter; var createTimer = require('./timer').createTimer; vows.describe('sub-events-order').addBatch({ 'calling `run` with 5 sec': { topic: function () { var promise = new EventEmitter(); var timer = createTimer(); timer.on('3', function (ret) { promise.emit('three', ret); }); timer.on('2', function (ret) { promise.emit('two', ret); }); timer.on('1', function (ret) { promise.emit('one', ret); }); timer.run(5, function () { promise.emit('success', timer); }); return promise; }, on: { 'three': { 'will emit `3` value': function (ret) { assert.equal(ret, 3); }, on: { 'one': { 'will emit `1` value': function (ret) { assert.equal(ret, 1); }, on: { 'two': { 'will emit `2` value': function (ret) { assert.equal(ret, 2); } } } } } } } } }).export(module);
$ vows subevents3.js --spec ♢ sub-events-order calling run with 5 sec on three ✓ will emit 3 value calling run with 5 sec on three on one ✓ will emit 1 value calling run with 5 sec on three on one on two ✗ will emit 2 value » two emitted before one ✗ Broken » 2 honored ∙ 1 broken (5.005s)
on の 文法構造
sub-events の最後の説明して、on 文法構造について説明します。on 文法構造ですが、github のここによると以下のような構造になっています。
- on は context のプロパティで、オブジェクトリテラルとして、context に1つ存在することができる
- on は最低1つ event を持つ必要があり、複数 event を持つことができる
- event は topic を持つことができない context でもある
以上の on 文法構造を整理すると、vows での文法構造は以下のようになります。
suite → batch*
batch → context*
context → topic? vow* context* on?
on → event+
event → context
on は実は、events のエイリアスで、上記の on を events に変更しても動作します。
connect-kyoto でセッション管理
kyototycoon でセッション管理する必要がでたので作ってみた。
githubに既にあるので、それ使えばよかったんだけど、何か coffeescript で書かれていて気持ちが悪い?のと、まだ npm になかったみたいなので、npm モジュール公開初デビューも兼ねてとりあえず作ってみた。
以下は、githubにあるREADME に書かれていることと同じ事かもしれないけど、使い方等の説明を書いておく。
事前に用意するもの
- kyotocabinet >= 1.2.63
- kyototycoon >= 0.9.45
とりあえず、これらがないと動かないでまずはインストールしておくこと。
バージョンについては、上に書いてあるバージョンになっているけど、
これはあくまでも、こちらで動作確認した際のバージョン。
kyototycoon の I/F は、get、set、clear、status、remove を使っており、xt オプションによる時限削除機能を使っているだけなので、最低限それらをサポートしているバージョンをインストールして使っても多分動くと思う。
インストール
以下のコマンドを叩くだけ。
$ npm install connect-kyoto
もし、connect-kyoto をグローバルに入れたい場合は、npm install に -g オプションして、インストール終わった後、npm link とかして使えばいいと思う。
指定できるオプション
TokyuStore 、じゃなくてw KyotoStore を new する際に指定できるオプションは、以下のとおり。
- port : kyototycoon のポート番号
- host : kyototycoon が動作しているホスト名
今のところ、2つだけど、db オプションでデータベースを指定できるよう今後する予定。
使い方
さあ、待ちに待った使い方。
使い方は、connect-redis や connect-mongodb とほとんど同じような使い方なので簡単。
KyotoStore を require でインポートして、connect.session あるいは、express.session で KyotoStore を new して、それを store に指定するという感じ。この辺は、connect や express にあるサンプルと同じ使い方です。コードにすると以下な感じ。
- connect
var KyotoStore = require('connect-kyoto').KyotoStore; // ... 何かの処理 connect( // ... 何かの処理 connect.cookieParser(), connect.session({ secret: 'youre secret here', cookie: { maxAge: 7 * 24 * 60 * 60 * 1000, // 1週間 }, store: new KyotoStore(), }), // ... 何かの処理 ).listen(3001);
- express
var KyotoStore = require('connect-kyoto').KyotoStore; // ... 何かの処理 app.configure(function(){ // ... 何かの処理 app.use(express.cookieParser()); app.use(express.session({ secret: 'your secret here', store: new KyotoStore(), cookie: { maxAge: 7 * 24 * 60 * 60 * 1000 // 1週間 }, })); // ... 何かの処理 });
とまあ、コードを書いて起動すれば使えるはず。起動の前には、ktserver コマンドによる kyototycoon の起動を忘れないでね!
kyototycoon で指定するデータベース
速度を求めるなら、オンメモリの ProtoHashDB、永続性を求めたいなら、HashDB とか。用は、まあ、自分が利用するユースケースに合わせればと。こちらと作者のブログをご参考に。
Node.js ではよく、redis とか mongodb とか使われているけど、kyototycoon(kyotocabinet) ではいろいろなデータベースや機能があって、速くて柔軟性が高いと思うので、個人的には KVS のデータベースとして Node.js でももっと使われてもいいような気がする。例えば、kyototycoon には、オンメモリスナップショットもあるし。
kinect on Mac OS X
何かと話題の XBOX360 の kinect 。先人の方の OpenKinect という Hack のおかげで、MacOSX、Linuxといった XBOX360 以外のプラットフォームでも使えるようになったらしい。kinect みたいなデバイスを使えば、何か面白そうなことができそうかもしれないので、とりあえず OpenKinect 入れてみた。
事前に必要なもの
OpenKinect をインストールするに当たって以下がないと駄目なのでインストールしておく必要がある。
- git (OpenKinect、libusbのパッチとかダウンロードするの必要)
- libusb (1.0.3 以上)
- cmake (2.6 以上)
macports とかでインストールすると良いだろう。
OpenKinect のダウンロード
git の clone で以下をダウンロードしよう。
$ git clone git://github.com/OpenKinect/libfreenect.git
$ git clone git://git.libusb.org/libusb.git
OpenKinect 用に libusb のパッチを適用する
ここによると、libusb に OpenKinect 用のパッチを適用しないといけないみたい。
パッチの適用はこんな感じで行う。
$ cd libusb
$ ./autogen.shpatch -p1 < ../libfreenect/platform/osx/libusb-osx-kinect.diff
$ ./configure LDFLAGS='-framework IOKit -framework CoreFoundation'
$ make
$ sudo make install
configure で LDFLAGS に "'-framework IOKit -framework CoreFoundation'" を指定しないと、この先 OpenKinect をインストールしてもうまく動かないので、必ず指定すること。
OpenKinect のインストール
libusb にパッチを適用したら、OpenKinect を以下のコマンドでインストール。
$ cd ../libfreenect/
$ mkdir build
$ cd build
$ ccmake ..
$ make
$ sudo make install
退職と転職のお知らせ
一部の方はご存知かと思いますが、このたびは10月31日づけで株式会社OKIネットワークス(以下、前職)を退職し、11月1日づけで株式会社BlueBridgeへ転職します。前職の業務は10月29日づけで終了です。
前職在職中にお世話になった皆さま、ありがとうございました。深く御礼を申し上げます。
前職でやっていたこと
前職では、コールセンタシステムであるCTstageというプロダクトの開発をしていました。
その中で自分がこれまでに開発(担当)してきたものなんですけど、こんな感じになります。
- コールセンタのオペレータさんが利用するアプリ開発
- コールセンタのスーパバイザさんが利用する管理ツールアプリの開発
- 着信呼をオペレータに分配する機能を持ったACDの開発
- 予測発信を自動的に行うプレディクティブダイヤリングの開発
- APIの開発
って具体的に何の技術とか使ってるとか分かりませんね。これじゃ。。。前職との守秘義務を考慮するとこれぐらいしか書けないです(> <)。すんません。技術的なキーワードを挙げるとすると、RPC、SIP、COM、.NET ですかね。
まあ、Windowsを使ってC/S型のシステムのフロントエンド、バックエンド、ミドルウェアの部分をひととおり開発やメンテナンスをしてきたんだ、と捉えて頂ければよろしいかと。そん中でも、フロントエンドの開発が割合が多かったのでそっちが得意。
前職で学んだこと
これまでの開発をとおして、ソフトウェア開発における一般的な知識や技術も学ぶことができたと思っていますが、一度リリースしたものに対するメンテナンスを意識した開発について一番学ぶことができたのではないかと思います。
他の機能に影響しないよう下位互換性を保ちながら機能追加やバグ修正したり、障害対応時に解析しやすいようにしたり等々。こういったメンテナンスよりの開発はどちらかというと泥仕事ですが、ソフトウェアというものはリリースして終わりというのではありません。実際に運用状態に入ってユーザに使ってもらってからが本番です(これはハードウェアを扱ったものにも言えることだと思います)。こういったメンテナンスよりの開発は、個人ではなかなかできないものなので、大変貴重な体験をさせて頂いたと思っています。大変感謝しています。
ジョブチェンジしたわけ
いろいろと理由があるのですが、一番の理由としては「ワクワクするような新しいことをやりながら、それを通じて自分も成長したい」。このヒトコトにつきるかと。
どちらかというと、自分は新しもの好きなフロンティア野郎なので、いつまでも同じことをやっていると何か腐ってしまいそうで。どちらかというと、教科書を読んではい!習得というより、実際に肌で体験しないと身につかない不器用な体質なんですよね。なので、新しいことにチャレンジして、失敗したら何か学んで次に結びつけていけたらいいかなと。
BlueBridgeへジョインするわけ
主に2つあります。1つは、創業者兼最高技術責任者クリストファー・テイト(以下、クリス)と一緒にやったら面白そうなことが出来そうだということ。もう1つはBlueBridgeのビジョン(=クリスの思想)が自分が考えていることを解決してくれそうだということ。
1つ目について。クリスとケイレキ.jp のiPhoneアプリの作成がきっかけで、彼がひとりで手がけているBlueBridgeのサービスや+@works的なサービスを、自分の本業とは別に一緒に開発にちょくちょく参加していました。クリスと開発を重ねるうちに、彼の天才的なコーディングや開発スタイルというシリコンバレー的な文化を感じれるのはもちろん、デザイナでもないのにユーザーフレンドリーなUIデザインを創り上げる能力など、プログラマとデザイナとの両方を兼ね備えているクリスといっしょにいると、自分としてはかなり刺激的。後、キャラが濃くて面白いですし(笑)。そんでもって、クリスはBlueBridgeのビジョンの実現に向けて、常にポジティブに試行錯誤しながら新しいことにチャレンジして、ケイレキ.jpやZooomrといったWebサービスやconnectFreeといったプロダクトを開発している。失敗を恐れずにチャレンジして、将来性のあるものが出来上がってきていくのは、クリスやBlueBridgeにとっても成長することだし、自分にとってもいいんじゃないかと。そしてエキサイティングなことでもあるし。これらがBlueBridgeにジョインしたくなった理由の1つ目。
2つ目について。ここのページにも書いてあるとおり、まだほとんどの人は世界中の情報を簡単に活用しきれていないとBlueBridge(クリス)は考えている。特にネットやWebをまだまだ活用しきれてないと考えている(実際にデジタルデバイドは世界的な問題だし日本でもそうですし)。いろんな人々が情報を簡単に活用できるように架け橋するのがBlueBridgeのミッション。自分は「ネットやWebを利用すればみんなハッピーになれると思うのに、なぜもっと利用しないんだろう?まだ複雑で難しいのかなあ。」と常々思っていました。そんなところにクリスと出会ったわけです。BlueBridgeのビジョンを見て「あっ、自分もBlueBridgeで働いて問題を解決するようなものを作れば、みんながハッピーになれるじゃん。」と思ったのがBlueBridgeにジョインしたくなった理由の2つ目。
かずぽんがBlueBridgeでチャレンジしていきたいこと
BlueBridgeのビジョンである、いろんな人が情報を簡単に利用できるようなサービスやプロダクトの開発。以上。
。。。って開発だけじゃなくて(笑)、前職で学んだメンテナンス周りの環境整備や、将来BlueBridgeのサービスやプロダクトを他の開発者が使ってもらえるような環境も整備していきたいなと思っています。また、CTO的な役割ができるようなことにもチャレンジしていきたいと思います。
2010 B1グランプリ
厚木で開催されたB1グランプリin厚木に参加してきました!
B1グランプリって何ぞよ?
wikipediaとか見てもらえば分かるかと思うんですが、簡単にいうとB1グランプリとは、B級ご当地グルメで町おこしをしている団体が、B級グルメで人気を競う競技の祭典です!
B1グランプリは実は今回で5回目で、第1回目では10団体でスタートして、今回の第5回では過去最多46団体の参加となりました!
B1グランプリで何してきたん?
B1級グルメのご当地を堪能してきました!
っていうのは冗談でw、B1グランプリに出展している八戸せんべい汁研究所のサポーターズクラブのなかの人として、お手伝いをしてきました!今年に入ってから、八戸せんべい汁研究所の一員であるなかの人としてお手伝いさせて頂いているのですが、今回は主にせんべい汁の盛りつけ担当でお手伝いしてきました!
せんべい汁って何だべ?
せんべい汁は、八戸の郷土料理で家庭料理でもあります。八戸出身の人なら知らない人はいないぐらい地元ではメジャー?な料理です。
せんべい汁は、鳥、豚、にんじん、ごぼう、ねぎ、キノコなどの具をベースにした醤油味の鍋料理で、それにせんべいが入っています。
鍋にせんべい入れたら、せんべいが溶けちゃうんじゃないの?と思いますが、せんべい汁に使うせんべいは普通に売っているせんべいと違って特別なせんべいで、最初からせんべいを入れて調理するのではなく、調理の最後らへんに入れて完成する料理なのです。
なので、せんべいを入れるタイミングで、せんべいをアルデンテにしたり、柔らかくしたりと、硬さを調節できます。ちなみに自分は、せんべいの硬さはアルデンテが好きです。
せんべい汁を実際に写真で見せるとこんな感じです!
この写真のせんべい汁は、八戸ニューシティホテルの食事処七重で頂いたせんべい汁です。
ちなみに、wikipediaに詳細内容が書いてあるみたいなので、興味がある人は見てみればいいと思います。
八戸せんべい汁研究所ってどんなラボ?
八戸の郷土料理である八戸せんべい汁を研究している団体です。
って書いたら、他のスタッフやに怒られてしまうので、簡単に補足的な感じで書かせていただきますと、地元八戸を愛してやまない人たちが普段は会社で働きながら、ボランティアでせんべい汁をPRするために活動している人たちの集まりです。
八戸せんべい汁研究所にいるスタッフの方は、飲食系の業界で別に片寄っているわけでなく、自分みたいなIT業界などの人たちがいるので、ある意味異業種の集まりにもなっています。なので、いろんな個性豊かな方が多くて面白い集まりでもあります。
B1グランプリで来てくださった方に食べて頂いてるせんべい汁は、八戸せんべい汁研究所で研究して作ったせんべい汁です。せんべい汁に入っている「せんべい」も、一般に購入できない「特注品」というこだわっています。
ちなみに、八戸せんべい汁研究所のスタッフの間では、八戸せんべい汁研究所のことを"汁研"と呼んでます。
以下は、八戸せんべい汁研究所関連のリンクです
今回のB1グランプリでどのぐらいせんべい汁だしたの?
18日、19日の2日間、B1グランプリがあったのですが、それぞれ5,000汁(汁研では"食"ではなく"汁"という単位で呼んでいます)、つまり、2日間で10,00011,000汁です!
この数は、B1グランプリのゴールドグランプリへの意気込みだと思っています。
実は、B1グランプリの企画したのは、八戸せんべい汁研究所なんです。過去の大会全て出場してきたのですが、シルバーグランプリは取れているのですが、未だにゴールドグランプリは取れていなかったのです。
で、今回のB1グランプリの結果は?
2010年度のB1グランプリの結果は、こんな感じになりました。
- 1位:みなさまの縁をとりもつ隊「甲府鳥もつ煮」(箸の重量:42,110g)
- 2位:ひるぜん焼そば好いとん会「ひるぜん焼そば」(箸の重量:41,520g)
- 3位:八戸せんべい汁研究所「八戸せんべい汁」(箸の重量:39,600g)
- 4位:津山ホルモンうどん研究会「津山ホルモンうどん」(箸の重量:35,130g)
- 5位:三浦中華料理研究会「三崎まぐろラーメン」(箸の重量:24,360g)
- 6位:いなり寿司で豊川市をもりあげ隊「豊川いなり寿司」(箸の重量:21,690g)
- 7位:やきそばのまち黒石会「黒石つゆやきそば」(箸の重量:18,750g)
- 8位:十和田バラ焼きゼミナール「十和田バラ焼き」(箸の重量:18,230g)
- 9位:みしまコロッケの会「みしまコロッケ」(箸の重量:17,850g)
- 10位:オホーツク北見塩やきそば推進協議会「オホーツク北見塩やきそば」(箸の重量:17,680g)
※1:来場者数(2日間合計):435,000人
※2:B1グランプリの投票の集計は箸の重量
八戸せんべい汁研究所は「3位」!!
う〜、1位との僅差は、約3,000g!
ゴールドグランプリではなく、ブロンズグランプリという結果になりましたが、今回も上位トップ3に入賞することができてよかったと思います!
今回のB1グランプリの経験で感じたこと
今回、八戸せんべい汁研究所のメンバとして初めてB1グランプリに参加させて頂きました。その中で改めて勉強させられたことがあります。
それは「みんなの心が1つになったときって本当に凄いなあ」ということ!
八戸せんべい汁研究所のスタッフのメンバは、普段は会社で勤務しながらボランティアで活動しています。スタッフは八戸の人だけでなく自分のように関東にいる人もいます。実際、今回は関東のスタッフが半数以上だったようでした。
普段あまり会わないスタッフメンバと、事前に集まって一緒に準備や打ち合わせをすることなく、こうした大きなイベントをこなすというのは普通できないです。これができたのも、スタッフメンバが全員「地元のためにグランプリとるぞ!」と心を1つにして、それぞれが自分の意志で目標に向かって行動できたからではないのかと。そう思ったのが、1日目、2日目のB1グランプリが終わってから、みんなと一緒に飲みに行ったとき。スタッフメンバ、みんな熱いんです!地元を良くしたいと、2日目はグランプリ撮るために意見を出し合ったりと。それぞれが思っていることを語ってくれるんです!だから、B1グランプリで必ず上位に入っているのではないかと思っています。
このB1グランプリで集まった八戸せんべい汁研究所のスタッフの熱い思いが、今後地元や関東や他の場所にいる人たちに伝わって、それぞれがアクションして欲しいなあと思っています。
pythonのインタラクティブモードで補完を有効にする方法
エキスパートPythonプログラミングで知ったのでメモしておく。
まあ、IPythonを使うことがほとんどだと思うけど。
コードの準備
以下のコードを準備。
# -*- coding: utf-8 -*- import readline import rlcompleter import atexit import os # tab complete readline.parse_and_bind('tab: complete') # history histfile = os.path.join(os.environ['HOME'], '.pythonhistory') try: readline.read_history_file(histfile) except IOError, e: pass atexit.register(readline.write_history_file, histfile) del os, histfile, readline, rlcompleter, atexit
上記コードが準備できたら、このコードをどっかにファイル名 .pythonstart で保存する。
ここでは説明のため、~/.pythonstartup に置くものとする。
パスの設定
.pythonstartup ファイルをどっかに置いただけでは、Python のインタラクティブモードで補完が有効にならない。このファイルへのパスを通すために環境変数を設定する必要がある。
環境変数の設定は、.bashrc や .bash_profile などに記載する。記載例は以下のとおり。
export PYTHONSTARTUP=~/.pythonstartup
ここの例では、~/.pythonstartup にパスが通されている。
実行結果
shell を再起動させて実際に動かしてみるとこんな感じになる。
macbook-2:~ kazupon$ python Python 2.5.5 (r255:77872, Aug 9 2010, 04:39:07) [GCC 4.2.1 (Apple Inc. build 5659)] on darwin Type "help", "copyright", "credits" or "license" for more information. >>> import datetime >>> datetime. datetime.MAXYEAR datetime.__dict__ datetime.__hash__ datetime.__reduce__ datetime.__str__ datetime.time datetime.MINYEAR datetime.__doc__ datetime.__init__ datetime.__reduce_ex__ datetime.date datetime.timedelta datetime.__class__ datetime.__file__ datetime.__name__ datetime.__repr__ datetime.datetime datetime.tzinfo datetime.__delattr__ datetime.__getattribute__ datetime.__new__ datetime.__setattr__ datetime.datetime_CAPI
はい以上。便利♪