アカベコマイリ

HEAR NOTHING SEE NOTHING SAY NOTHING

espower-babel から babel-preset-power-assert への移行

私は Web フロントエンドのテストで mocha + power-assert + espower-babel を組み合わせて利用しているのだが、これらのうち espower-babel が Deprecated になった。経緯と代替については以下の記事にまとまっている。

これからは espower-babel 代わりに babel-registerbabel-preset-power-assert を利用。babel-register で require をフックして assert を power-assert へ置き換える。この処理が Babel ビルドの一環として実行されるようになった、という理解でよいのだろうか。

移行には npm 更新、.babelrcmocha.opt 修正が必要。これを一発で実行するため migrate-espower-babel-to-babel-preset-power-assert というツールも用意されている。冒頭の記事にも使用方法が掲載されているけど自分でも試したいので内容を以下にまとめる。

migrate-espower-babel-to-babel-preset-power-assert

このツールは既に mocha + power-assert + espower-babel を利用してるプロジェクトを新構成へ移行するためのもの。複数プロジェクトで利用したいのでグローバルにインストール。

$ npm i -g migrate-espower-babel-to-babel-preset-power-assert

次に移行したいプロジェクトの package.json の置かれた階層へ移動する。そして以下のようにコマンドを実行。

$ migrate-espower-babel-to-babel-preset-power-assert
Run: npm uninstall --save-dev espower-babel
Run: npm install --save-dev power-assert
Run: npm install --save-dev babel-preset-power-assert
Run: npm install --save-dev babel-register
rewrite mocha.opts
rewrite .babelrc

注意点がある。

test ディレクトリ内に mocha.opts が存在しないとエラーになる。空の mocha.opts を配置した場合は出力に rewrite と表示されもののファイルは空のまま。---compilers js:espower-babel/guess を定義してから実行したら ---compilers js:babel-register へ書き換えられた。

私は mocha.opts を利用せず npm-scripts の test で対応していたのでエラーになった。mocha についてこれを期に mocha.opts を使うようにするか npm-scripts 完結のままにするか迷っている。

このコマンドを繰り返し実行すると .babelrcenv.development.presets に power-assert が複数追加される。移行ツールなのでコマンド実行は一度で十分だが mocha.opts の有無による動作検証で何度も実行していたらこの問題に遭遇した。

手動による導入 or 移行

移行ツールでおこなっていることをそのまま手動で実行。

はじめに espower-babel をアンインストール。このコマンドは npm がなければ空振りするだけなので導入時に実行しても問題ない。

$ npm uninstall -D espower-babel

テストに必要な npm をインストール。テスト対象とテストの両方を ES2015 で実装、かつ React の JSX を含むので、そのためのプリセットも含めている。

$ nmp i -D mocha power-assert babel-preset-power-assert babel-register
$ npm i -D babel-preset-es2015 babel-preset-react

package.json と同一階層に .babelrc を作成、以下を記述して保存。

{
  "presets": [
    "es2015",
    "react"
  ],
  "env": {
    "development": {
      "presets": [
        "power-assert"
      ]
    }
  }
}

mocha のオプションを test ディレクトリ内の mocha.opts で指定するならファイルを作成。以下を記述して保存。

---compilers js:babel-register
test/**/*.test.js

私のように mocha の実行とオプションを package.json の npm-scripts で一括指定するなら以下のように記述。mocha.opts ありなら test コマンドの内容はオプションなしで mocha だけとすること。

{
  "scripts": {
    "test": "mocha --compilers js:babel-register test/**/*.test.js"
  }
}

mocha.opts、npm-scripts ともに babel-register のフックを有効にして test 配下の *.test.js というファイルをテスト対象にしている。わざわざ特別な拡張子にしている理由は以下。

  • テキスト エディタのタブ上でテスト対象 *.js とテスト *.test.js を区別したい
  • モックやユーティリティなど、テストから参照されるがテスト コードを含まないファイル *.js をスキップしたい

既存プロジェクトで移行が正常であることを試すため test コマンドを実行。

$ npm test

> electron-starter@1.0.7 test .../examples-electron/electron-starter
> mocha

  Util
    formatDate
      ✓ Default YYYY-MM-DD hh:mm:ss.SSS
      ✓ Hyphen YYYY-MM-DD-hh-mm-ss
      ✓ No zero-padding YYYY/M/D h:m:s

  3 passing (17ms)

ばっちり。npm-scripts のみと mocha.opts の両方を試したが、どちらも正常にテスト実行された。

ES2015 と assert

今回のテストに使用したコードは以下となる。

import assert from 'power-assert';
import Util   from '../../src/js/common/Util.js';

/** @test {Util} */
describe( 'Util', () => {
  /** @test {Util#formatDate} */
  describe( 'formatDate', () => {
    it( 'Default YYYY-MM-DD hh:mm:ss.SSS', () => {
      const date = new Date( 2015, 7, 4, 21, 17, 45, 512 );
      const text = Util.formatDate( date );
      assert( text === '2015-08-04 21:17:45.512' );
    } );

    it( 'Hyphen YYYY-MM-DD-hh-mm-ss', () => {
      const date = new Date( 2015, 7, 4, 21, 17, 45, 512 );
      const text = Util.formatDate( date, 'YYYY-MM-DD-hh-mm-ss' );
      assert( text === '2015-08-04-21-17-45' );
    } );

    it( 'No zero-padding YYYY/M/D h:m:s', () => {
      const date = new Date( 2015, 7, 4, 21, 17, 45, 512 );
      const text = Util.formatDate( date, 'YYYY/M/D h:m:s' );
      assert( text === '2015/8/4 21:17:45' );
    } );
  } );
} );

見てのとおりテスト自体も ES2015 で記述されている。espower-babel と同じくテストと対象の両方で ES2015 を利用可能。プリセットを変えれば ES2016 や Polyfill などもゆける。

また、

import assert from 'power-assert';

import assert from 'assert';

でもよい。実際に import を標準 assert へ書き換えてから、わざとテストを失敗させてみる。

  1 failing

  1) Util formatDate Default YYYY-MM-DD hh:mm:ss.SSS:

      AssertionError:   # test/common/Util.test.js:11

  assert(text === '2015-08-04 21:17:45.512')
         |    |
         |    false
         "2016-08-04 21:17:45.512"

  --- [string] '2015-08-04 21:17:45.512'
  +++ [string] text
  @@ -1,12 +1,12 @@
   201
  -5
  +6
   -08-04 2

      + expected - actual

      -false
      +true

      at decoratedAssert (node_modules/empower/lib/decorate.js:42:30)
      at powerAssert (node_modules/empower/index.js:65:32)
      at Context.<anonymous> (Util.test.js:11:7)

エラー情報が詳細に表示されている。冒頭の記事で解説されていたように assert そのものが power-assert 化されていることがわかる。

assertimport するようにしておけば将来 power-assert から標準 assert へ戻したくなった時もコードを書き換えずに済む。例えば標準 assert が power-assert 並に高機能化するとか、むしろ power-assert の実装を取り込むとかしたときには脱却することとなるかもしれない。

まとめ

espower-babel に比べて設定量は増えたが Babel を使うなら .babelrc は必要なので、そこに集約したほうが分かりやすくなる。

プロジェクトにあまりファイルを増やしたくないので mocha.opts については採用すべきか迷うところ。しかし既に .babelrc だけでなく esdoc.json なんかも追加しているので、そういうものだと受け入れることにした。

Babel については package.jsonbabel プロパティでも代替できるらしい。けれど他のツールもこの方式をサポートしていなと管理がバラバラになりかえって混乱しそうだ。私の理想は

{
  "tools": {
    "babel": {},
    "browserify": {}
    "esdoc": {},
    "mocha": {}
  }
}

のような感じでツール専用のプロパティが提供され、その配下に Babel などのプロパティが属する設計。しかし既に実運用されているものを後付で統一されたルールに移行させるのは高い政治力が求められる。また package.json の巨大化も受け入れられなさそうだから妄想にとどめておく。

babel-preset-power-assert 移行によりテスト対象の assert も power-assert 化されるのはよい。デバッグ ビルドだけ assert を有効にする (= リリース ビルドでは除去) なら邪魔にならないし、テスト時に assert がヒットしたら詳細な情報にあたれるので assert を積極的に書きたくなる。

最近の Web フロントエンド開発はビルドとプリプロセッサーを前提とすることで IDE のように高度な依存注入や変換を利用できて生産性が増している。ありがたいことだ。

最後に移行の実装例となるプロジェクトを挙げておく。