power-assertとmochaを使ったnodejsのテスト
昨年decodeではじめて和田卓人さんの講演を聞いて、テスト駆動開発というものを知りとても興味が湧いた。テスト環境づくりは多くのサイトで紹介されているのだけど、自分の備忘録として環境構築手順を残しておこうと思う。もちろんアサーションツールには、power-assertを使用する。
大きな流れ
- npmで必要なモジュールのインストール
- npm scriptsでテスト実行コマンド登録
- テストコードを書く
- テスト実行
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(固有の処理)を行ってくれる。
- 作者: David Mark Clements,和田祐一郎
- 出版社/メーカー: オライリージャパン
- 発売日: 2013/02/23
- メディア: 大型本
- 購入: 2人 クリック: 4回
- この商品を含むブログ (5件) を見る
Promiseでループ処理
javascriptで通信処理を記述するときに使用するPromiseオブジェクト。
だいぶ慣れてきたが、ループ処理についてはよくわからず立ち止まってしまったので解決を考えてみたいと思う。
満たしたい要件
- ループ内の処理はPromiseを返却する
- ループ回数を指定できる
- ループ内処理に、異なるパラメータを渡せる(1)
- ループ内でエラーが起きた場合、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めっちゃ便利。
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()