vows に追加されているアンドキュメントな機能

JavaScript Advent Calendar 2011 (Node.js/WebSocketsコース) 12日目の記事です。
Node.js におけるテストフレームである、vows について書いてみました。

はじめに

この記事では、README.md本家 Web サイトのドキュメントに載っていないことを主に書いています。
最新バージョンである 0.6.0 の vows を元に、おさらいした後、以下の3つについて説明します。

  1. assert
  2. teardown
  3. 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 で非同期イベントをテストするには、

  1. topic の this.callback 関数
  2. 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 でチェックしているようです。

assert.matches(actual, expected, [message])

actual に 正規表現で指定された expected でマッチするかどうかチェックします。
assert.matches は assert.matche のエイリアスですので、assert.matche と同じです。
条件が合致しない場合は、message を出力します。

assert.matches('hello world', /^[a-z]+ [a-z]+$/);

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

&#9826; teardown

  context
    &#10003; vow1
    &#10003; vow2
context teardown !!
 
&#10003; OK &#187; 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

&#9826; teardown

  context
    &#10003; vow1
    &#10003; vow2
  context subcontext
    &#10003; nested vow
subcontext teardown !!
context teardown !!
 
&#10003; OK &#187; 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

&#9826; teardown

  context1
    &#10003; vow1
  context2
    &#10007; vow2
      &#187; expected false, got true // teardown2.js:29
 
&#10007; Broken &#187; 1 honored &#8729; 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

&#9826; teardown

  context1
    &#10003; vow1
teardown !!
  context2
    &#10003; vow2
 
&#10003; OK &#187; 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

&#9826; sub-events

  calling run with 5 sec on three
    &#10003; will emit 3 value
  calling run with 5 sec on two
    &#10003; will emit 2 value
  calling run with 5 sec on one
    &#10003; will emit 1 value
 
&#10003; OK &#187; 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

&#9826; sub-events-order

  calling run with 5 sec on three
    &#10003; will emit 3 value
  calling run with 5 sec on three on two
    &#10003; will emit 2 value
  calling run with 5 sec on three on two on one
    &#10003; will emit 1 value
 
&#10003; OK &#187; 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

&#9826; sub-events-order

  calling run with 5 sec on three
    &#10003; will emit 3 value
  calling run with 5 sec on three on one
    &#10003; will emit 1 value
  calling run with 5 sec on three on one on two
    &#10007; will emit 2 value
      &#187; two emitted before one
 
&#10007; Broken &#187; 2 honored &#8729; 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 に変更しても動作します。

おわりに

以上、vows のアンドキュメントな機能について説明しましたが、いかがでしょうか?

  • 便利な assert
  • 後始末に便利な teardown
  • そして、非同期イベントのテストにますます便利な sub-events

これらを活用すれば、vows で今よりも効率よく単体テストが書けるんではないでしょうか。
この記事が、楽しい Node.js のプログラミングに役立てればと思います!
Enjoy Node.js Programming !!
:)

これまでのソースコードは、gist のここに置いてあります。

connect-kyoto でセッション管理

kyototycoon でセッション管理する必要がでたので作ってみた。

githubに既にあるので、それ使えばよかったんだけど、何か coffeescript で書かれていて気持ちが悪い?のと、まだ npm になかったみたいなので、npm モジュール公開初デビューも兼ねてとりあえず作ってみた。

以下は、githubにあるREADME に書かれていることと同じ事かもしれないけど、使い方等の説明を書いておく。

事前に用意するもの

とりあえず、これらがないと動かないでまずはインストールしておくこと。
バージョンについては、上に書いてあるバージョンになっているけど、
これはあくまでも、こちらで動作確認した際のバージョン。
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-redisconnect-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 には、オンメモリスナップショットもあるし。

フィードバッグ

できたてほやほやなので、github の issue やら、twitter でも何でもいいので、フィードバッグ頂けれると助かります!(><)

その他

このブログエントリとは関係ないけど、10/29(土)に Node.js 日本ユーザグループが主催する東京Node学園祭が行われます。Node.js の作者とか Socket.IO の作者がくるんで、Node.js に熱い方は、せひご参加を!参加特典として、ファーストサーバさんと Joyent さんが協力して Node.js 専用ホスティングサービスを参加者全員に無償提供するみたいです!

kinect on Mac OS X

何かと話題の XBOX360kinect 。先人の方の OpenKinect という Hack のおかげで、MacOSXLinuxといった 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

動作確認

OpenKinect のインストールとともに、動作確認用?の glview というアプリケーションがインストールされているみたい。
kinectMacbook に接続して、以下の glview というコマンドを実行するとこんな感じのアプリケーションが起動する。

おお、動いた! 素晴らしい!

今後

このページや、本家の情報によると、openframeworks などのライブラリがあるみたい。ちょっといじって遊んでみようかと思います。

退職と転職のお知らせ

一部の方はご存知かと思いますが、このたびは10月31日づけで株式会社OKIネットワークス(以下、前職)を退職し、11月1日づけで株式会社BlueBridgeへ転職します。前職の業務は10月29日づけで終了です。
前職在職中にお世話になった皆さま、ありがとうございました。深く御礼を申し上げます。

前職でやっていたこと

前職では、コールセンタシステムであるCTstageというプロダクトの開発をしていました。
その中で自分がこれまでに開発(担当)してきたものなんですけど、こんな感じになります。

  • コールセンタのオペレータさんが利用するアプリ開発
  • コールセンタのスーパバイザさんが利用する管理ツールアプリの開発
  • 着信呼をオペレータに分配する機能を持ったACDの開発
  • 予測発信を自動的に行うプレディクティブダイヤリングの開発
  • APIの開発

って具体的に何の技術とか使ってるとか分かりませんね。これじゃ。。。前職との守秘義務を考慮するとこれぐらいしか書けないです(> <)。すんません。技術的なキーワードを挙げるとすると、RPC、SIP、COM、.NET ですかね。
まあ、Windowsを使ってC/S型のシステムのフロントエンド、バックエンド、ミドルウェアの部分をひととおり開発やメンテナンスをしてきたんだ、と捉えて頂ければよろしいかと。そん中でも、フロントエンドの開発が割合が多かったのでそっちが得意。

前職で学んだこと

これまでの開発をとおして、ソフトウェア開発における一般的な知識や技術も学ぶことができたと思っていますが、一度リリースしたものに対するメンテナンスを意識した開発について一番学ぶことができたのではないかと思います。
他の機能に影響しないよう下位互換性を保ちながら機能追加やバグ修正したり、障害対応時に解析しやすいようにしたり等々。こういったメンテナンスよりの開発はどちらかというと泥仕事ですが、ソフトウェアというものはリリースして終わりというのではありません。実際に運用状態に入ってユーザに使ってもらってからが本番です(これはハードウェアを扱ったものにも言えることだと思います)。こういったメンテナンスよりの開発は、個人ではなかなかできないものなので、大変貴重な体験をさせて頂いたと思っています。大変感謝しています。

ジョブチェンジしたわけ

いろいろと理由があるのですが、一番の理由としては「ワクワクするような新しいことをやりながら、それを通じて自分も成長したい」。このヒトコトにつきるかと。
どちらかというと、自分は新しもの好きなフロンティア野郎なので、いつまでも同じことをやっていると何か腐ってしまいそうで。どちらかというと、教科書を読んではい!習得というより、実際に肌で体験しないと身につかない不器用な体質なんですよね。なので、新しいことにチャレンジして、失敗したら何か学んで次に結びつけていけたらいいかなと。

BlueBridgeへジョインするわけ

主に2つあります。1つは、創業者兼最高技術責任者クリストファー・テイト(以下、クリス)と一緒にやったら面白そうなことが出来そうだということ。もう1つはBlueBridgeのビジョン(=クリスの思想)が自分が考えていることを解決してくれそうだということ。


1つ目について。クリスとケイレキ.jpiPhoneアプリの作成がきっかけで、彼がひとりで手がけている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:来場者数(2日間合計):435,000人
※2:B1グランプリの投票の集計は箸の重量


八戸せんべい汁研究所は「3位」!!
う〜、1位との僅差は、約3,000g!
ゴールドグランプリではなく、ブロンズグランプリという結果になりましたが、今回も上位トップ3に入賞することができてよかったと思います!

今回のB1グランプリの経験で感じたこと

今回、八戸せんべい汁研究所のメンバとして初めてB1グランプリに参加させて頂きました。その中で改めて勉強させられたことがあります。
それは「みんなの心が1つになったときって本当に凄いなあ」ということ!

八戸せんべい汁研究所のスタッフのメンバは、普段は会社で勤務しながらボランティアで活動しています。スタッフは八戸の人だけでなく自分のように関東にいる人もいます。実際、今回は関東のスタッフが半数以上だったようでした。
普段あまり会わないスタッフメンバと、事前に集まって一緒に準備や打ち合わせをすることなく、こうした大きなイベントをこなすというのは普通できないです。これができたのも、スタッフメンバが全員「地元のためにグランプリとるぞ!」と心を1つにして、それぞれが自分の意志で目標に向かって行動できたからではないのかと。そう思ったのが、1日目、2日目のB1グランプリが終わってから、みんなと一緒に飲みに行ったとき。スタッフメンバ、みんな熱いんです!地元を良くしたいと、2日目はグランプリ撮るために意見を出し合ったりと。それぞれが思っていることを語ってくれるんです!だから、B1グランプリで必ず上位に入っているのではないかと思っています。


このB1グランプリで集まった八戸せんべい汁研究所のスタッフの熱い思いが、今後地元や関東や他の場所にいる人たちに伝わって、それぞれがアクションして欲しいなあと思っています。

最後に

B1グランプリでお世話になったせんべい汁研究所みなさま、後B1グランプリの関係者のみなさま、大変お疲れさまでした!
そして、B1グランプリに来てくださった方、ありがとうございました!

最後に、ちょっと真面目っぽくなってしまったので、最後に自分の iPhone で撮った写真を載せておこうかと思います。


並んでるお客様の様子です。


せんべい汁の仕出しをしながら、肉を解凍しているマ汁ガーZです。


戦いを終えたマ汁ガーZです。


藤川優里議員も応援しています。


地元の方の応援メッセージです。


トリオ・ザ・ポンチョスのマネをしている私です。w

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     

はい以上。便利♪