power-assertとmochaを使ったnodejsのテスト

昨年decodeではじめて和田卓人さんの講演を聞いて、テスト駆動開発というものを知りとても興味が湧いた。テスト環境づくりは多くのサイトで紹介されているのだけど、自分の備忘録として環境構築手順を残しておこうと思う。もちろんアサーションツールには、power-assertを使用する。

大きな流れ

  1. npmで必要なモジュールのインストール
  2. npm scriptsでテスト実行コマンド登録
  3. テストコードを書く
  4. テスト実行

1 npmで必要なモジュールのインストール

// mochaのインストール
npm install --save-dev mocha

// power-assertのインストール
npm install --save-dev power-assert intelli-espower-loader

2 npm scriptsでテスト実行コマンド登録

testフォルダを作成して、このフォルダをテスト対象として実行するようにnpm scriptsに記述する。

// package.json
  ...
  "scripts": {
    "test": "mocha --require intelli-espower-loader ./test/"
  },
  ...

3 テストコードを書く

テスト対象のコードはただの計算クラスで、2つの引数を足したり引いたりするだけ。

class Calc {

  static add(x, y) {
    return x + y;
  }

  static sub(x, y) {
    return x - y;
  }

}
exports.Calc = Calc;

テストコードでは、power-assertと上記テスト対象のコードをロードして、あとはテストコードの記法通り。本筋とはずれるけど、オブジェクトの分割代入便利ですね。

const assert = require('power-assert');
const {Calc} = require('../module/Calc');  // 分割代入

describe('Calcクラス', () => {

  it('addメソッド', () => {
    assert( Calc.add(1,2) === 3 );
  });


  it('subメソッド', () => {
    assert( Calc.sub(1,2) === -1 );
  });

});

4 テスト実行

あとは、npm testでテスト実行できる。
実行結果はこんな感じ

 Calcクラス
    ✓ addメソッド
    ✓ subメソッド

  2 passing (9ms)

疑問

  • DBアクセスするコードのテストはどうすればよいのだろうか?
  • テストごとにDBを初期化しテスト終了後も初期化する作業が必要なのか?

expressのルーティング共通処理

nodejsでHTTPサーバーを立てるときに利用するexpress。
色々なルーティングを記述していくと、いずれのルーティングでも共通処理を噛ませたいという気持ちになる。

共通処理というのは、セッションが切れていたときにログイン画面に戻す処理のことで、今までは下記のように個々のルーティングごとに記述するアホなことをやっていた。

app.post('/select',  ( req, res ) => {
  
  // ①共通処理:セッション情報の存在判定
  if ( !req.session.pass ) {
     res.json(440, {result:'expired', message:'セッションが切れました。ログインからやり直してください。'});
     return;
   }

  // ②ルーティング固有の処理
  res.header("Content-Type", "application/json; charset=utf-8");
  res.json( something );

}

Nodeクックブックを読んでると、next()を使えばよいことがわかった。

// ①共通処理:セッション情報の存在判定
const checkSession = ( req, res, next) => {
  if ( !req.session.pass ) {
     res.json(440, {result:'expired', message:'セッションが切れました。ログインからやり直してください。'});
     return;
   }
  next()
} ;

app.post('/select', checkSession,  ( req, res ) => {
  
  // ②ルーティング固有の処理
  res.header("Content-Type", "application/json; charset=utf-8");
  res.json( something );

}

expressのpost, getなどのHTTPメソッドには、第二引数以降に複数のcallbackを指定でき、next()を使うことで順番に処理を行ってくれる。
今回では、checkSession(共通処理)がまず実行されsession状態が切れていなければ、next()により次のcallback(固有の処理)を行ってくれる。

Nodeクックブック

Nodeクックブック

Promiseでループ処理

javascriptで通信処理を記述するときに使用するPromiseオブジェクト。
だいぶ慣れてきたが、ループ処理についてはよくわからず立ち止まってしまったので解決を考えてみたいと思う。

満たしたい要件

  1. ループ内の処理はPromiseを返却する
  2. ループ回数を指定できる
  3. ループ内処理に、異なるパラメータを渡せる(1)
  4. ループ内でエラーが起きた場合、catchできる

単純にループを

まず1,2を満たすコードを書いてみる。

// ループ内処理
// @param {Number} i - ループカウンター
function action(i) {
  return new Promise( (res, rej) => {
    setTimeout( () => {
      console.log(i + '回目です')
      res();
    },500)
  })
}

// ループ関数
// @param {Function} fn - 個々の処理
// @param {Number} i - カウント初期値
// @param {Number} end - カウント終了値
function loop( fn, i, end) {

  return fn(i)
  .then( () => {
    if ( i < end ) {
      return loop( fn, i+1, end)
    }
    else {
      return Promise.resolve('end');
    }
  })

}

// 実行
loop(action, 0, 3 );

// console
< 1回目です
< 2回目です
< 3回目です
< 4回目です
< end

ループ内処理では、setTimeoutを使用して擬似的に遅延させてPromiseを返却している。
Promiseでのループ関数のポイントは再帰処理だと思ってる。ループ内処理を呼び出して、thenでPromiseをチェーンさせる2。then内で引数の値からloopを終了させるかどうか判定し、続けるならばloopを再帰的に呼び出し、終了ならばPromiseを返すようにした。

エラー処理の追加

続いて、ループない処理でエラーが生じた場合それ以降の処理を中断するように改良する。

function action(i) {
  return new Promise( (res, rej) => {
    setTimeout( () => {
      // 修正
      if ( i === 5 ) {
        rej('5は処理できません');
      }
      else {
        console.log(i + '回目です')
        res();
      }
    },500)
  })
}

function loop( fn, i, end) {

  return fn(i)
  .then( () => {
    if ( i < end ) {
      return loop( fn, i+1, end)
    }
    else {
      return Promise.resolve('end');
    }
  })
  // 追加
  .catch( (err) => {
    console.log(err);
  })

}

// 実行
loop( action, 0, 6);
// console
< 0回目です
< 1回目です
< 2回目です
< 3回目です
< 4回目です
< 5回は処理できません

ループ内処理では、5だとrejectするように擬似的に設定。 ループ関数では、最後にcatchを追加するだけでよい。

任意のパラメータ指定

最後に3.の指定した任意のパラメータを実行させるように改良してみる

// ループ内処理
// 引数を追加
function action(i, second) {
  return new Promise( (res, rej) => {
    setTimeout( () => {
      if ( i === 5 ) {
        rej('5回は処理できません');
      }
      else {
      console.log(i + '回目です');
      // 追加
      console.log('パラメータ合計:' +  Number(i + second) ); 
      res();
      }
    },500)
  })
}

// ループ関数
// params引数を追加
function loop( fn, params, i, end) {

  // applyを使って、個々のparamを渡す
  return fn.apply(null, params[i])
  .then( () => {
    if ( i < end ) {
      return loop( fn, params, i+1, end)
    }
    else {
      return Promise.resolve('end');
    }
  })
  .catch( (err) => {
    console.log(err);
  })

}

// 個々のパラメータを用意
var params = [
  [1,10],
  [2,20],
  [3,30],
  [4,40]
]

// 実行
loop(action, params, 0, 3 )
// console
< 1回目です
< パラメータ合計:11
< 2回目です
< パラメータ合計:22
< 3回目です
< パラメータ合計:33
< 4回目です
< パラメータ合計:44
< end

修正はループ関数だけでよい。
むしろ、ループを回すために内部処理に微修正させるとかしたくない。 fnを直接呼び出すのではなく、applyメソッドでparamsをfnの引数として 利用するように指定する。applyめっちゃ便利。


  1. ループ内処理は同じ処理である

  2. チェーンさせて処理をつなげるため、whileやfor文ではループ処理ができないのだと思っている。

underscore.jsを使って特定のvalueをもつkeyのみ取得する方法

最近jsを書いているとき、可能であれば関数型プログラミングを心がけている。
そのほうが一時的な変数を書かなくてい済ませるし、
やはり書いていて気持ちがよい笑

今回は、 underscore.jsを使ってオブジェクト内の 特定のvalueをもつkeyのみを取得する方法がわかったので記録しておく

特定のvalueをもつもののみ取得する

var obj = {
  A : '1',
  B : '1',
  C : '0',
  D : '1'
};

_.pick( obj, function (v) {
  return v === '1'
})

// console
> { A : '1', B : '1', D : '1' }

keyだけがほしい場合

_.chain(obj)
 .pick( function (v) {
    return v === '1'
 })
 .keys()
 .value()

// console
> ["A", "B", "D"]

アロー関数を使うともっと短くなる

_.chain(obj)
 .pick( v =>  v === '1' )
 .keys()
 .value()