JavaScript 向け vim 設定とか unit test とか

ご無沙汰しております。Over80です。

年単位で放置していた当ブログですが、更新再開してみようと思います。一発目の今日は、JavaScript 関連の雑多ネタです。

JavaScript でブラウザ上で動くゲームを作りたい、というところが発端なんですが、 JavaScript はあまり書いたことが無いので、ある程度以上の規模のコードを書くのであればと、開発環境からきちんと揃えていました。

主な内容は、

  • 良い感じのコーディングスタイルを確認しときたい
  • それを確認する lint ツールも欲しい
  • 快適に書けるエディタとかIDEとかあるの?
  • unit test もできるようにしときたい

とかいろいろと調べた内容のまとめです。

コーディングスタイル

JavaScriptは文法制限が緩い言語なので、どこまででも汚く書くことができるわけですが、ある程度律しておかないと自分で読めなくなります。

こういうのは、既にいろいろな人に揉まれてベストプラクティスになっているものに乗っかるのが基本でしょう。ざっと調べた限りでは、以下の2つが良い感じでした。

前者の Node.js の方が、短くて、必要十分な最低限の記述なので、これをベースにしたいところですが、三項演算子とネストのあたりに異議ありです。後者の Google の方はざっと読んだ限り異議はないんですが、詳細すぎてちょっと辛い感じ。まぁ、自分が困らない程度に緩く守る程度にして、書き方に迷ったらこれらを適宜参照しつつ書いていこうと思います。

lint ツール

不慣れな言語なので、単純な文法ミスや、前述のスタイルから外れてしまったときに警告してくれるツールは欲しいものです。

いくつかありますが、これも上記のコーディングスタイルとの親和性から、Google のツールを使うことにします。

インストールは上記ページにあるとおり、easy_install で一発。(easy_install については昔このブログでもネタにしました。)

使い方も gjslint というプログラムにファイルやディレクトリを渡すだけです。単純なスタイルエラーであれば、自動で補正してくれる fixjsstyle というプログラムも同梱されています。

vim の設定

エディタも専用のもので良い感じのものがあればと軽く探してみましたが(ググるとこんな記事とかもある http://matome.naver.jp/odai/2137182325409098101 )、結局、キーワードを補完して欲しいのであればIDEを、そうでなければ慣れたエディタをカスタマイズして使えば? ということになっているようです。

というわけで、今回は後者で、vim に最低限の設定だけして済まします。
個人的には欲しいのは:

  • シンタックスの色付け
  • インデント幅とタブ幅の設定
  • 改行時にインデント幅を維持
  • lint ツールの実行結果を表示

ぐらいです。最後の lint ツール連携だけは、自分が不慣れなので、という意図が強いですが、それ以外はどんな言語でも最低設定しているものです。

具体的な .vimrc (の今回関連する部分の抜粋)は以下のとおり。

autocmd FileType javascript setl autoindent 
autocmd FileType javascript setl smartindent 
autocmd FileType javascript setl tabstop=8 expandtab shiftwidth=2 softtabstop=2 
execute pathogen#infect() 
let g:syntastic_javascript_checkers=['gjslint'] 

syntax 色付けは、以前ネタにしたので省略。autoindent と tab設定系も python を例に書いてますね。cinwords は設定していませんが、デフォルトのままでも、軽く書いた分には気にならなかったので、良いことにします。

4行目と5行目は lint ツールとの連携のための設定ですが、私は Syntastic https://github.com/scrooloose/syntastic を使っています。
インストール方法などはオフィシャルページ参照で、4行目はそこで指示されていたとおりの記述です。5行目は、「javascript だったら gjslint で文法チェックしてね」と syntastic に指示するものです。Syntastic をインストールした状態で適当な JavaScript ファイルを開き、

:SyntasticInfo

とコマンドを打ち込むと、以下のように情報が表示されますので、うまく動かない時は確認してみるのが良いでしょう。

Syntastic: active mode enabled             ← ファイル保存時に自動でチェックする
Syntastic info for filetype: javascript    ← そのファイルをどの言語と認識しているか
Available checker(s): gjslint              ← そのPCにインストールされている、その言語用の lint ツール
Currently enabled checker(s): gjslint      ← そのなかでどれをチェックに使用するか。
                                              上記の .vimrc の5行目で、これが設定される。

unit test

これもまた、いろいろなテストツールがあるんですが、個人的な要件としては

  • Node.js でもHTML埋め込みでも使用可能
    • ゲームのロジック部分はUI部分と分離して、コンソール上で Node.js & ユニットテストでがりがりと開発したいので、Node.js 対応は必須。加えて、同じテストフレームワークがUIのテストまでカバーできるとうれしい。
  • xUnit 系の流儀から極端に外れていない
    • 学習コストが安い。選択を間違った場合にも、別のものに乗り換えが容易。
  • 十分に小さい or 十分に stable
    • 本家が開発やめちゃった時など、最悪、自分で抱え込んでも苦にならない程度のもの。

といった所です。

で、とりあえず目についたのが、Sink Test というテストフレームワークです。

Ubuntuでは Universe に node-sink-test という名前でパッケージ化されているので、 apt でインストールも容易ですし、フレームワーク自体がファイル1個なので、使いたい所にコピーして回るのでも構わないでしょう。(MITライセンスのようです。)

使い方は上記のオフィシャルな README.md を読めば大体わかりますが、Node.js のモジュールとしても、HTMLに埋め込んでも動作するようになっていますので、先に挙げた希望はだいたい満足しています。

サンプルも付属していますが、長大すぎるので、自分用のテンプレートとして以下のようなものを書いてみました。

var sink = require('sink');
var start = sink.start;
sink = sink.sink;

sink('test case class', function(test, ok, before, after, assert) {
  before(function() {
    // setUp function
  });

  after(function() {
    // tearDown function
  });

  test('synchronous test', function(complete) {
    assert.ok(1 == 1, 'always true');
    complete(); // must call it at the end of the test.
  });

  test('asynchronous test', 2/* number of expected assertions */, function() {
    setTimeout(function() {
      assert.ok(true, 'callback #1');
    }, 0);
    setTimeout(function() {
      assert.ok(true, 'callback #2');
    }, 0);
  });

});

sink('another test case', function(test, ok, before, after, assert) {
  test('available assertions', function(complete) {
    assert.ok(1 == 1, 'assert.ok()');
    assert.equal(1/* actual */, 1/* expected */, 'assert.equal()');
    assert.notEqual(2, 1, 'assert.notEqual()');
    assert.deepEqual([1, {2: 3}], [1, {2: 3}], 'assert.deepEqual()');
    // assert.notDeepEqual() is not supported.
    assert.strictEqual(1, 1, 'assert.strictEqual()'); // compared by ===
    assert.notStrictEqual([], [], 'assert.notStrictEqual()');
    // assert.throws() is not supported.

    complete();
  });
});

start();

といったところで、今回は以上です。