yo_waka's blog

418 I'm a teapot

デザイン変えた

テーマストア見てたらなんか意欲が湧いてきたのでデザインを弄ってみた。

ブログのテーマデザインを弄くるのは楽しい。
まだプログラムの勉強やり始めてまもない頃、バイトでMTのテーマとかプラグインやら作ってたのを思い出す。
そういえばあのころ、ブログが広まりだしたころって3カラム全盛期だったなあ。
今はあまり見かけないけど、よく調整されたやつは下にスクロールすると1カラムぽく見えるので、2カラムを下にスクロールするのに比べて違和感なくて好きだった。

vagrantとknife-soloで開発環境構築を自動化するやつ

最近vagrantとknife-soloで、ってのが流行ってるみたいなのでやってみた。
waka/auto-dev-env

ベースBoxは、vagrantbox.esからUbuntu12.10 server minimalってのを落とせるけど、knife-soloはOmibus Chef Packagingのものをインストールしてくれるのでchefは入ってる必要なかったり、Boxのディスクサイズが10GBと少なかったので、Veeweeを使って作った。
vagrantはポートフォワーディングの設定が簡単で便利ですね。

とにかく、これでムシャクシャしたとき「sudo rm -rf /」がいつでも出来るようになったぞ!

Opscodeのレシピ見ててもRedhat系かDebian系かで動かなくなりそうなもの多いので、もし作ったレシピを配布して開発環境合わせるとかやろうとすると、実行するマシンのOSと仮想環境のディストリビューションを統一するのが大事なんだろうな。

Google Closure LibraryのユニットテストをPhantomJSで実行するやつ

を2年前くらいに作ったのだけど、久々にGoogle Closure Libraryを使いたくなって、そうなるとテストも書かないとなので引っ張りだして使おうと思ったのですが、なにせ2年前に作ったものなので、ソースコードはレガシー感が漂ってて、使ってるPhantomJSのAPIもdeprecatedになっていたりしたのでいろいろ直してアップデート(リポジトリ名も少し変更)。

waka/closure-library-phantomjs

単一のテストファイルのテストでもマルチテストランナーを使った複数テストファイルのテストでも同じ使い方です。
詳しい使い方やオプションは、READMEに書いた。

最近Mochaでテストを書くことが多いのですが、出力形式をいろいろ選べるの楽しくていいなと思ったので、インターフェース切ってリポーターをいくつか使えるようにした。

実際よく使うのは、見やすいSpec形式と、Jenkins等で扱うためのTAP形式と、テスト数が多い場合のDot形式かなと思ったので、その3つを使えるようにした。
これで、手元での開発に加えて、JenkinsでもTAP-Plugin使ってCIしやすくなった。

昔はQt入れなきゃいけなかったりして、PhantomJSのビルドが結構大変だった記憶があるのだけど、今のPhantomJSはバイナリが提供されていて、すぐに使えて便利。
Node.jsが入ってる環境なら、npmでも入れられる。
Node.jsが使える環境で、コマンドラインからもっと手軽に使いたい場合は、npmにもアップしてあるのでそっちからも使えます。
npm/closure-library-phantomjs
(npmからインストールしない場合はlib以下のJSをどこかに置いてPhantomJSから起動すれば使えます)

PhantomJSの変わった点といえば、CommonJSのmodule1.1をサポートするようになったのは大きい。
これによって起動スクリプト内で他のスクリプトを読み込めるので、起動スクリプトを分割しやすくなった。

あと、1.2から追加されたWebpageモジュールのinjectJSがかなり素晴らしい。
ClosureLibraryのテストランナーはブラウザ実行、つまりDOMに結果を書き出すのが前提で、そのままPhantomJSのconsoleにテストランナーのconsole.log出力を流しても欲しいログが取れない。

なので、テストの実行時間やエラーのスタックトレースを取得するために、goog.testing.TestRunnerやgoog.testing.MultiTestRunnerを拡張したりメソッドを差し替えたり、ということを以前はテストHTML内にスクリプトタグを追加してやっていたのだけど、これだとテストファイル1つ1つに差し込んでいかないといけないので効率が悪いし差し込むの忘れる。

そこでWebpage#onInitializedのハンドラでinjectJSを使うと、ページがロードされてからwindow.onloadが走る直前にスクリプトを差し込めて実行できる。これでテストランナーのscriptタグが読み込まれてオブジェクトを参照可能かつテストランナーが走る前、というタイミングでメソッドを拡張したり差し替えることができます。素晴らしい。

Google Closure LibraryでUI作ってユニットテストも書いて、、って開発しているとこなんてほとんど無いと思うけど、Google Closure Libraryのマルチテストランナーはかなり使い勝手がよいですし、非同期テストもサポートされていてアサーションも完備。

たしかQUnitは複数のHTMLテストファイルを実行できなかったと思うので、複数実行したい場合のテストライブラリとしてGoogle Closure Libraryを使うというのもアリなんじゃないかと思います。
よく飼い慣らされたClosure Library野郎であれば、当然テストのためだけにClosure Libraryを使う。

bowlというNode.jsのcluster管理モジュールを作った

cluster周りのコードを書いていて、ワーカー周りの死活監視やエラー処理などいつも同じようなこと書いてるのでモジュール化してみました。
bowl

GitHubにソースコードもあげてあります。
安心のテストコード付き!
waka/node-bowl

できること

  • コマンドラインスクリプトからの起動
  • ワーカーが死んだら新しいワーカーを立ち上げ
  • 停止シグナル(SIGINT, SIGQUIT)
  • SIGUSR2シグナルによるGraceful Restart(この辺にも対応
  • 指定したディレクトリ/ファイルに変更があったら再起動(Graceful Restart)
  • プラグインによるマスタープロセスの拡張
  • pidファイルの書き出し(monitなどの監視を考えて)

設定の外部ファイル化や機能のオン/オフなどもできるので、詳しい使い方はヘルプコマンドにまとめた。

開発メモ

ファイルの変更監視にはfs.watchFile、使えない環境ならfs.watchを利用します。
node-devみたいにmodule.requireを書き換えるやり方もありますが、デフォルトの挙動を差し替えるのは開発環境でしか使えないよなーと思って、ディレクトリとファイルを指定するやり方にしました。
ディレクトリは、子階層もちゃんと見るようにしています。
ただ、fs.watchFile/watchの仕様ぽいんだけど、ファイルの削除とリネームは変更イベントが起きない。ディレクトリで指定するとイベント発生するのに・・!要調査。

forkするワーカーの起動時にエラーが起きた場合は、domainのエラーハンドラでは対処出来ないので、起動後10秒以内に10回以上ワーカーのエラーが発生したら終了するようにしています。

また、Node.jsの対応バージョンが0.8.xとしているのは、ロガーとして使っているwinstonを0.9.xで動かしたところどうもファイルのtransportを使うと最初の1回しか書き込まれない現象が起きているから。
winstonは連続でログを書き込もうとしたときにキューに溜めておいて、drainイベントで書き込めるようになったら書き込みを再開するのだけど、drainイベントがなぜか発生しない。
0.9.xからはstream周りがかなり変わるみたいな話をどこかで見た気がしたので、ソースを追ってみようと思ったけどかなり複雑でちょっと後回しにしちゃった。
winstonはtransportを柔軟に設定可能なところと、ログローテートが出来る点が好きなライブラリなので、ちょっと時間ができたらちゃんと追ってみよう。

今回テスト用に初めてTravis CIを使ってみたけど、.travis.yml置いてプッシュするだけとか簡単すぎてやばい。
GitHub周りのエコシステムは仕組みといい使い勝手といい素晴らしいな。

Node.jsアプリで結果が非同期になる箇所はコールバックを渡せるようにした方がいい

既存のNodeアプリのテストを書いていて思ったことをメモ。
書いてるうちに別にNode.jsに限った話じゃなくて、JavaScript全般に言える話じゃんと思ったけどまいっか。

Node.jsで書くアプリは中の処理で非同期API使われてると、テストを書くのがとても難しいというかめんどくさい。

例えばfsモジュールで非同期でファイル書いてる処理があって、もし書き込みに失敗したらエラーイベントを投げて上のレイヤーで処理する、みたいなのがあるとする。

// HogeクラスはEventEmitterを継承している
Hoge.prototype.writeFile = function(file, data) {
  var self = this;
  fs.writeFile(file, data, function(err) {
    if (err) {
      self.emit('error.file', err.message);
    }
  });
};

アプリ側では"error.file"イベントを捕まえてよしなにやるからいいんだけど、テストを書こうとするととてもめんどくさい。
ファイルに書き込んだら適当な時間待ちつつ、エラーイベントが捕まったら失敗にする。

// mochaを使った例
it('ファイル書けるか', function(done) {
  var timer;
  var hoge = new Hoge();
  hoge.on('error.file', function(errMessage) {
    clearTimeout(timer);
    assert.ok(false, errMessage);
  });
  hoge.writeFile('./test.txt', 'test');
  timer = setTimeout(function() {
    done();
  }, 1000);
});

適当な時間待つのがとてもスマートじゃない感じ。。。
これだけだったらいいけど、他にもいろんなところで非同期APIを中で使ってるとテストコードがsetTimeoutの嵐に><

この問題は、アプリ側の方でもコールバックを取れるようにしてあるといい感じにできる。

Hoge.prototype.writeFile = function(file, data, opt_callback) {
  var self = this;
  fs.writeFile(file, data, function(err) {
    if (err) {
      self.emit('error.file', err.message);
    }
    opt_callback && opt_callback(err || null);
  });
};

// テスト
it('ファイル書けるか', function(done) {
  var hoge = new Hoge();
  hoge.writeFile('./test.txt', 'test', function(err) {
    if (err) {
      assert.ok(false, err.message);
    }
    done();
  });
});

タイマー処理の必要がなくなり、とてもスッキリと書けるようになりました。
普段からNodeアプリを作ってる人には当たり前の話かもしれませんが、要は中で非同期API使ってるメソッドは外側からコールバックを渡せるようにしておくとテスト書きやすいですという話。
アプリで使ってなくても、オプション引数としてJSDocに書いておけばよいですし。
新しくNode.jsアプリを作るときはコーディング規約にしたいなー

既存のアプリですべての該当箇所を変えるのはそれはそれでいろいろ大変ですが。。