Node.js Advent Calendar 2013 5日目 の記事です。

power-doctest

power-doctest という doctest風 にコードと評価結果のコメントを並べるとテストとして実行出来るようなNode製のツールを作りました。

評価したい式; // => 期待する評価結果

の形式のコードをそのままテストとして実行するようなツールです。

ScreenShot

使い方

npm install -g power-doctest

npmでインストールできます。

以下のような感じでコードを書いて、これを main.js として保存しておきます。

function sum(ary) {
    return ary.reduce(function (current, next) {
        return current + next
    }, );
}

var total = sum([1, 2, 3, 4, 5]); total; // > 5

そして、ファイル名を指定してテストを評価します。

$ power-doctest main.js

上記の例では total; // > 5 という部分が、 total === 5 であるかをテストした結果が表示されます。この場合はテストが失敗するので以下のようなエラーが出力されます。

$ power-doctest main.js
FAIL # at line: 13

assert(total === 5); | | 15 false

また以下のように書いても問題ありません。

sum([1, 2, 3, 4, 5]);// > 5

この場合は以下のようなエラー表示になります。

assert(sum([1,2,3,4,5]) === 5);
       |                |
       15               false

上記のエラー表示を見るとわかりますが、power-assert を使ったアサーションが行われるようになっています。


こっから先はどういう思想で作ったのかや仕組みなどの話になっています。

面倒な人は以下のスライドに簡略化された概要が載ってるので、そちらをみるといいかもしれません。

目的

このツールの目的として書籍のサンプルコードの写経などでの動作確認を楽にすることがあります。

多くの書籍では、紙面のスペースの都合等もありますが、以下のように実行結果を横にコメントで書くようなケースが多いと思います。

Effective javascript 2013 12 01 21 43 23

これを写経して試す際には、ブラウザやNodeのREPLなどで試す必要があってエディタから離れないと行けないので不便な感じです。

また、書籍のサンプルコードはテストをちゃんと書いてないケースもあるので、動かないサンプルコードというのも結構ある感じがします。

power-doctest はそういうの補助するために作ったdoctestっぽいツールで、以下のように

評価したい式; // => 期待する評価結果

というのが、そのままテストとして実行されるようになっています。

Effective JavaScript 2013 12 01 21 49 24

ScreenShot

デフォルトでは(今は切り替えとかないですが)、power-assertを使っているので、単純な式なら間違っていた時にかなり分かりやすい結果を表示できます。

かなり単純な仕組みなので、ライブラリ等のプロダクトコード的なテスト用途にはあんまり考えてないです。

他のJavaScriptのdoctestツールとしては以下のようなものがあります。

  • dtao/autodoc
    • JSDocの @example をテストする感じのドキュメントツール
  • davidchambers/doctest
    • ASTを書き換えてテストするツール. ちょっと欲しかったものとは趣旨が違う構造だったのでforkしなかった
  • Doctest.js: the humane Javascript test framework
    • 書き方もよく似てて色々細かい処理もできるみたいだけど、動かしてないので分からない。
    • CLIというよりはブラウザで動かす感じっぽい

仕組み

上記のスクリーンショットを見て気づく人もいると思いますが、JavaScript ASTを書き換えて行っています。

評価したい式; // => 期待する評価結果

というものを見つけたら(=>以外も->や>でも大丈夫だけど)、

assert(評価したい式 === 期待する評価結果);

という風に書き換えているだけなので、これをnodeのvmモジュールで実行しています。

NodeのvmモジュールではJavaScriptコード文字列をファイルから読み込んで実行したかのようにでき、またcontext(アクティベーションオブジェクトとか実行コンテキスト言われてたようなやつ)を設定できるので、グローバルにいるようなものを書き換えたりした状態で、コードを動かすことが出来ます。

power-doctest は グローバルにassert (power-assert)を追加するという手法を取ってますが、この方法が最善なのかよくわかってないので、もっといい方法があったら知りたいです。
(行番号がずれるとかある)

vmモジュールを利用すれば、以下のようにプライベートメソッドのテストもできたりするようです。

JavaScript ASTについては以下の記事を見るといいかと思います。

power-doctestではコメントも扱えるtraversalライブラリのmillermedeiros/rocamboleを使ってます。
(弊害なのかわからないですが、コメントを維持したままコードを生成できてない…)

仕組みとしては、assertへの書き換えとvmモジュールを使った実行 だけなので、大体1日ぐらいでできた感じです。

Todo

突貫で作ったのでまだ良くない部分もあります。

  • Try-catch等の例外のサポート
  • サポートしました => オブジェクト同士の比較と例外のテストをサポートしたpower-doctest 0.2.0リリース | Web scratch

    蛇足 – 写経側

    わざわざdoctest風にしないでも、mocha等を使ってインラインにテストを書けばもっと自由に細かくテストを出来ます。
    Functional JavaScriptを写経した際 にはそのような方法をとって、気になったこともコメントとして埋め込んでgrocを使ってドキュメントページとして見られるような感じにしてました。

    この方法は、Ch.4.Higher-OrderFunctions.jsなどを見るとわかりますが、コードが結構ごちゃごちゃするのであんまり見やすくないです…

    また、Functional JavaScriptは関数型スタイルの書籍だったので関数という単位がコードになってたのでテストも書きやすいですが、普通の書籍はもっとバラバラなのでいちいちassertionとかを書くと大変になると思います。

    蛇足 – 書く側

    自分でThe little book of Buster.JSのような電子書籍を書いてる時は、書籍内に出てくるサンプルコードが正しく動くことを保証するために以下のような事をしてました。

    書籍自体(azu/busterjs-ebook)はSphinxを使って書いていた。

    普通にrstでインラインにコードを書くのも出来ましたがサンプルコードは別のリポジトリ(azu/busterjs-kumite)に用意して、Sphinxの機能でコードを読み込んで表示するようにしていました。

    そして、サンプルコードはそれぞれテストを動かして意図した通りの動作になってるかを確認できるようにしてました(そもそもテストについて書いてたので、テストをするテストを書いてたりしましたが)

    なので、書籍とか書く人はちゃんとサンプルコードのテストもかいてあげるといいと思います。
    (サンプルコードを配布する事例は多いですが、テストが入ってるケースは意外と少ない)

    power-doctest のような簡易なものでもある程度意味あるとは思うので、何か上手く行く仕組みがあればもっとテスト付きのサンプルコードが増えるかなと思ったのも作ったきっかけではあります。

    書籍じゃなくても readme とかでも同じことが言えて、vesln/jsmdのようにreadmeに書かれているサンプルコードをテスト出来るようなツールも存在します。
    (readmeは更新されてなくて、古いコードが載ってて動かないケースが多い)

    このように、今なら大体の場所に書かれているコードを動かす方法があると思うので、動かないコードを減らせればいいなーと思います。

    JS testing everywhere!

    2015-08-15 追記: power-assertを使ったDoctestツール power-doctestを書き直した | Web Scratch