読者です 読者をやめる 読者になる 読者になる

頭の整理

JavaScript, Ruby, RSpec, Node.js, Rails TDDなどに興味があるWeb系SEが学んだことを整理していきます

power-assert使ってみた - 東京Node学園 10時限目に参加してきました

東京Node学園 10時限目に参加してきました.
東京Node学園 10時限目 - connpass
そのなかで@t_wadaさんが紹介されていて個人的に興味があったJavaScriptのテスト用ライブラリpower-assertを使ってみました.
具体的にはNode.jsのテストフレームワークmocha用に書いたコードをpower-assertで実行してみました.

power-assert作成の背景

@t_wadaさんが発表でおっしゃっていたpower-assert作成の背景を自分なりにまとめてみます.

テストライブラリを選ぶときは常に書きやすさと失敗時の情報量を天秤にかける必要があります.
例えば,以下のようなテストコードを書いたとき.

 var assert = require('power-assert');

 describe('Array', function(){
   beforeEach(function(){
     this.ary = [1, 2, 3];
   });
   describe('#indexOf()', function(){
     it('should return index when the value is present', function(){
       var zero = 0, two = 2;
       assert(this.ary.indexOf(zero) === two);
     })
   })
 });

このテストを実行すると以下のような結果が得られます.

$ mocha test/power_assert_test.js

  ․

  0 passing (4 ms)
  1 failing

  1) Array #indexOf() should return index when the value is present:
     AssertionError: false == true
(以下略)

「AssertionError: false == true」という出力がされますが,これだけではなぜ失敗したのかがわかりにくいです.また,assertメソッドの引数はどちらが期待値でどちらが実際値を入れるべきなのか間違えやすいですし,テストフレームワークによって違ったりもします.
じゃあRspec風の「expect( ).to ...」のような書き方ができればいいのではと考えると,Matcherとか書き方とかを覚えるのが大変です.

そこで,assertionの出力の情報量が多ければ暗記量が少なくて書きやすくなり,失敗時の情報量も多くなってわかりやすくなるのではという発想でpower-assertを作成したとのことでした.

つまり,書きやすさと失敗時の情報量の多さどちらも得られるのがpower-assertの特徴ということになります.

power-assertの準備

実際にpower-assertを使うための準備をします.

package.jsonを準備

プロジェクトを新規で作る場合は下記のコマンドを実行していくつかの設定項目を入力すると自動でpackage.jsonを生成してくれます.

$ npm init

または,package.jsonを以下の内容で保存します.

{}

package.jsonがないと次の手順で npm install したときにエラーになってしまいます.

npm install

プロジェクトのルートディレクトリで以下のコマンドを実行します.

$ npm install power-assert grunt-espower --save-dev

すると,package.jsonは以下のように追記されるはずです.

{
  "devDependencies": {
    "power-assert": "~0.1.5",
    "grunt": "~0.4.1",
    "grunt-espower": "~0.1.5"
  }
}

Gruntfile.jsの準備

gruntのtaskによってpower-assert用のファイルを生成するため,Gruntfile.jsを以下のように記述します.

module.exports = function(grunt) {
  var pkg = grunt.file.readJSON('package.json')
    , taskName
  ;

 grunt.initConfig({
   espower: {
     test: {
       files: [
         {
           expand: true, //Enable dynamic expansion,
           cwd: 'test/', //Src matches are relative to this path
           src: ['**/*.js'], //Actual pattern(s) to match
           dest: 'espowered/', //Destination path prefix
           ext: '.js' //Dest filepaths will have this extension
         }
       ]
     }
   }
 });

 for (taskName in pkg.devDependencies) {
   if (taskName.substr(0, 6) == 'grunt-') {
     grunt.loadNpmTasks(taskName);
   }
 }
};

これによって,testディレクトリ以下のテストファイルを基にしてespoweredディレクトリにpower-assert用のテストファイルが作成されるように設定できます.

テストを書く

出力の比較のために,power-assert作成の背景の節で記したテストコードをtestディレクトリにpower_assert_example-test.jsという名前で保存します.

以上で準備は完了です.

テストファイル生成と実行

gruntタスクを走らせてテストコードを生成します.

$ grunt espower:test
Running "espower:test" (espower) task

Done, without errors.

上記のように表示されればタスク実行が正常に完了しています.

テストを走らせてみます.

$ mocha espowered/power_assert_example-test.js

  ․

  0 passing (6 ms)
  1 failing

  1) Array #indexOf() should return index when the value is present:
     AssertionError: # (プロジェクトのルートディレクトリ)/test/power_assert_example-test.js:10

      assert(this.ary.indexOf(zero) === two);
                  |   |       |     |   |
                  |   |       |     |   2
                  |   -1      0     false
                  [1,2,3]
(以下略)

assertの中身の情報が出力されていて,[1, 2, 3]というaryで呼び出したindexOfというメソッドの結果が-1になっていて右辺の2とは一致せずに失敗していることが分かります.

power-assertが何をやっているか

gruntのtaskでは以下のようなことをやっているみたいです.
記述したテストコード(今回ではtestディレクトリ以下に保存したファイルの中身)をパースしてAST(abstract syntax tree, 抽象構文木)にする.次にASTからpower-assert用のテストメソッドが使える形のテストコードに変更・生成する.

そして,生成されたテストコードを実行することで先述のような出力が得られるという仕組みだと思います.

※注意
READMEにも書いていますが,この記事の執筆時点ではpower-assertはまだα版なので使い方や動作などは変わる可能性があります.