ES6 コードをテストする
ES6 で書かれたコードをユニット テストしたい。できればテスト自体も ES6 で。という希望を実現してくれそうなツールがあったので試してみる。
mocha
ユニット テストには mocha を利用する。業務で Node モジュールのテストに利用していて馴染みがあるのと後述する espower-babel が mocha を想定しているのがその理由。
mocha を npm test
や npm run
から利用するならインストールはローカルだけでよい。package.json
管理下にある npm にはパスが通った状態になる。
$ npm i -D mocha
余談だが以下の記事を読んで gulp などもローカルにインストールして実行を npm で抽象化するほうがよいのでは?と考えるようになった。
記事中にもメリットとして説明されているとおり利用者は npm だけ覚えればよい。グローバルな npm 依存を避けられるのもよいことだ。難点は gulp の default
タスクを gulp
で呼び出すのに比べて npm start
はすこし長くてタイプが面倒なぐらいか。些末な問題だが。
espower-babel
espower-babel は ES6 で書かれたコードとユニット テストを実行するための機能を提供してくれる。使い方は以下の記事が参考になる。
- power-assert-js/espower-babel
- ライブラリをES6で書いて公開する所から始めよう
- テストコードをES6+power-assertで書けるespower-babel 3.0.0リリース
初期バージョンではユニット テストのみ ES6 で対象となるコードが ES6 の場合は事前に ES5 変換しておく必要があったらしい。現行のものはどちらも ES6 のままでよいとのこと。ふつうに Node や ES5 のテストを書く感覚で運用できて便利だ。
これもローカルにインストールする。
$ npm i -D espower-babel
mocha と espower-babel を組み合わせて npm test
からテストを起動するコマンドを package.json
の scripts
へ記述。この定義はプロジェクトのルート直下にある test
フォルダ内に置かれたユニット テストを起動する。
{
"scripts": {
"test": "mocha --compilers js:espower-babel/guess test/**/*.js"
}
}
ES6 を ES5 にコンパイルしたものを mocha に渡す、という動作になるようだ。
- 補足 2015/7/14 私のサンプルではテスト用ファイルを
*.test.js
と命名しているが、このようにするなら espower-babel に指定するパスもtest/**/*.test.js
にしたほうがよい。こうするとテストだけで使用するユーティリティ JS をtest
内に置いたとしても、それをテスト対象から除外できる。
power-assert
power-assert はシンプルな assert
メソッドと豊富なエラー情報を提供してくれる。
私は Node の assert
でも equal
や throws
ぐらいしか利用していない。またユニット テストでは成功より失敗時の情報を詳しく知りたいことが多いので power-assert を採用することにした。なお throws
や doesNotThrow
なども標準 assert
互換のインターフェースが提供されている。
power-assert はユニット テストから参照するだけなので、これもローカルにインストールすればよい。
$ npm i -D power-assert
テストを書いてみる
実際にテストを書いてみよう。まずプロジェクトは以下のように構成。
/
├ package.json
├ README.md
├ js/
│ ├ sample.js
│ └ util.js
└ test/
└ sample.test.js
package.json
の定義は以下。
{
"name": "es6-unit-test",
"version": "1.0.0",
"description": "Sample for unit test in ES6.",
"main": "index.js",
"scripts": {
"test": "mocha --compilers js:espower-babel/guess test/**/*.js"
},
"keywords": [
"ES6",
"test"
],
"author": "akabeko",
"license": "MIT",
"devDependencies": {
"espower-babel": "^3.1.1",
"mocha": "^2.2.5",
"power-assert": "^0.11.0"
}
}
テスト対象を実装する。ES6 modules によるファイル参照も試したいので 2 ファイルを用意した。まずは util.js
。例外もテストするため、想定外の型をもつ引数が指定されたときに発行している。
class Util {
sum( a, b ) {
if( !( typeof a === 'number' && typeof b === 'number' ) ) {
throw new Error( 'Invalid argument type of not Number.' );
}
return ( a + b );
}
}
// Singleton
export default new Util();
そういえば以前 ES6 でシングルトンを実現するとき Symbol を利用していた。しかし Flux の dispatcher サンプルなどを読むと単純に class
を new
して export
するだけでよいらしい。シングルトンの是非はさておき最近はこの方式で定義している。
次は sample.js
。さきほどの util.js
を import
している。また、このスクリプトではクラスだけでなく関数も export
。default
とそれ以外をユニット テスト側で個別に import
可能か試したい。
import Util from './util.js';
export default class Sample {
sum( a, b ) {
// ES6 modules を試すため、別クラスのメソッドを呼ぶ
return Util.sum( a, b );
}
exists( arr, target ) {
if( !( arr && 0 < arr.length && arr.indexOf ) ) { return false; }
return ( arr.indexOf( target ) !== -1 );
}
}
// 関数を公開
export function Floor( value ) {
return Math.floor( value );
}
ユニット テストは以下のように実装。power-assert の assert
と throws
を利用。
import assert from 'power-assert';
import Sample from '../js/sample.js';
import {Floor} from '../js/sample.js';
describe( 'Sample.sum()', () => {
it( '合計', () => {
const sample = new Sample();
const sum = sample.sum( 1, 2 );
assert( sum === 3 );
} );
it( '不正な型による例外発行', () => {
assert.throws(
() => {
const sample = new Sample();
sample.sum();
},
Error
);
} );
} );
describe( 'Floor()', () => {
it( '整数化', () => {
const value = Floor( 42.195 );
assert( value === 42 );
} );
} );
これら 3 ファイルはすべて ES6 のコードになる。果たして無事にテストが走るだろうか?ドキドキしながら実行。
$ npm test
> es6-unit-test@1.0.0 test .../es6-unit-test/src
> mocha --compilers js:espower-babel/guess test/**/*.js
Sample.sum()
✓ 合計
✓ 不正な型による例外発行
Floor()
✓ 整数化
3 passing (8ms)
ばっちり。では意図的にテストを失敗させてみよう。「合計」で実行している Sample.sum
の第一引数を null
にしてからテストを再実行するとテストが失敗する。
Sample.sum()
1) 合計
✓ 不正な型による例外発行
Floor()
✓ 整数化
2 passing (18ms)
1 failing
1) Sample.sum() 合計:
Error: Invalid argument type of not Number.
at Util.sum (js/util.js:18:13)
at Sample.sum (js/sample.js:19:17)
at Context.<anonymous> (test/sample.test.js:8:27)
npm ERR! Test failed. See above for more details.
発行された例外メッセージと共に発生箇所が詳細に出力されている。ES6 to 5 変換により行数がズレるかも?と思っていたが適切な位置を指してくれた。実に分かりやすい。
まとめ
Browserify/babelify を採用してからユニット テストについて悩んでいた。mocha だけでは ES6 をテストできない。そのため生成されたファイルに対してテストするとか考えたけど面倒そうで躊躇していた。
今回の方法であれば、Browserify/babelify のようなプリプロセスや周辺環境を意識することなく、直に ES6 コードをテストできるので楽ちん。
今回作成したサンプルを公開しておく。