power-assert + babel as a development tool
3行まとめ
- espower-babelは役目を終えたので、Deprecated
- Babel + power-assertはbabel-preset-power-assertを使う
- コード上は
require("power-assert")
とrequire("assert")
どちらでもpower-assert化できるようになった
espower-babelは非推奨へ
Babel + Mocha + power-assertの組み合わせを出来るだけ設定ファイルなどを作らずに使えるespower-babelというモジュールを書いていましたが、役目を終えたので非推奨(deprecated)にしました。
理由としては、Babel@6からは設定(ファイル)を必ず必要とするので、espower-babelをかませる分、柔軟性がなくなったり余計な処理が起きて遅くなるためです。
代わりにbabel-registerとbabel-preset-power-assertを直接使って、開発時のBabelのビルド設定としてpower-assertを導入する方法を推奨しています。
以下は、power-assert + Mocha + Babel環境を新規インストールする場合の手順ですが、espower-babelからの移行はmigrate-espower-babel-to-babel-preset-power-assertを使うことで同様のことができるようにしてあります。
$ npm i -g migrate-espower-babel-to-babel-preset-power-assert
$ cd <該当プロジェクトrootへ>
$ migrate-espower-babel-to-babel-preset-power-assert
でマイグレーションしてくれます。 (自分のプロジェクトをespower-babelから移行するのに書いたので、人により構成が違うため動かないかもしれません。その場合はPull Requestをいただけると助かります。)
新規で、power-assert + Mocha + Babel環境(ランタイム変換)を導入する手順です。
サンプルプロジェクト
設定方法
必要なモジュールをインストール
npm i -D power-assert mocha babel-register babel-preset-power-assert babel-preset-es2015
mochaからテストを実行する際にBabelの変換をするので、babel-register
と以下の2つのpresetをインストールします。
- babel-preset-es2015
- ES2015の変換を行うpreset
- babel-preset-power-assert
- power-assert関連のpreset
.babelrc
を作成
Babelの設定をするために、.babelrc
を次のように作成します。
power-assertは開発ビルド(テスト中)にしか必要ないので、env
で振り分けしておきます。
env
はNODE_ENV
によって振り分けされます。 NODE_ENV=production <コマンド>
のような感じで環境変数を指定し、必要なプラグインを分けることができます。
何も指定していない場合はNODE_ENV=development
と同じになります。
{
"presets": [
"es2015"
],
"env": {
"development": {
"presets": [
"power-assert"
]
}
}
}
mocha.opts
を作成
Mochaの設定をするmocha.opts
を作成します。
test/mocha.opts
に以下のように書くだけです。設定ファイルとして作らないで、引数に渡すだけでいいかもしれません。
--compilers js:babel-register
こうすることで、Mochaのテスト実行時はBabelによる変換がランタイムで行われます。
テストを書く
次のような足し算をするコードを書いてみます。
add.js
:
"use strict";
const assert = require("assert");
export default function add(x, y) {
assert(typeof x === "number");
assert(typeof y === "number");
return x + y;
}
これをテストするコードをassert
モジュールを使って書いてみます。
add-test.js
// require("power-assert") じゃなくて assert でもいい
const assert = require("assert");
import add from "../src/add";
describe("add", function () {
it("should return x + y", function () {
const result = add(1, 2);
assert.equal(result, 5);// <= Wrong
});
});
$ mocha
という感じでテストを実行してみると、このテストは3 == 5
となっているので失敗します。
テストを失敗すると、power-assertにより構造的な結果が表示されています。
.babelrc
の設定にもとづいて変換する際にbabel-preset-power-assert
が次の2つのことをやっています。
require("assert")
をrequire("power-assert")
に書き換えるassert
関数などに仕込みをして、失敗した時に情報をいっぱい表示できるようにする- こちらは今までもやっていたこと
これにより、require("power-assert")
ではなく、ただのrequire("assert")
もpower-assert化された状態でassertionが行えるようになっています。
通常今までは、テストコードのみrequire("power-assert")
をして、アプリケーションコード側ではrequire("assert")
をしていたと思います。
例えば、このadd.js
の例では引数のチェックにassert
を使っています。
"use strict";
const assert = require("assert");
export default function add(x, y) {
assert(typeof x === "number");
assert(typeof y === "number");
return x + y;
}
なので、次のようなコードを書くと例外を投げるのでテストは失敗します。
it("arguments should be type of number", function () {
add("string", "string");
});
今までは、ただのassert()
だったので、大した情報はでませんでした。
しかし、これもMocha経由で実行してみると、assert(typeof x === "number");
がpower-assert化されています。
これはどういうことになるかというと、コード上でライブラリとしてrequire("power-assert")
と読み込む必要がなくなり、ツールとしてpower-assertが動くようになった事を意味します。
なので、power-assert
を使わなくなった時はツール(具体的にはbabelrc
から)power-assertを外すだけで、コードは一切変更しなくても良くなったということです。
そのため、power-assertは開発用のライブラリからツールという位置づけに変わったという話でした。
サンプルプロジェクト
おまけ
アプリケーションにassert
を書くようになったと言っても、今回のadd(x ,y)
のように決まりきったような引数のチェックを毎回書くのは大変です。
先ほどのadd.js
は次のような感じでテストすることができます。
const assert = require("assert");
import add from "../src/add";
describe("add", function () {
it("should return x + y", function () {
const result = add(1, 2);
assert.equal(result, 3);
});
it("arguments should be type of number", function () {
try {
add("string", "string");
throw new Error("unreachable line");
} catch (error) {
assert.equal(error.name, assert.AssertionError.name);
}
});
});
引数の型違反例外(assert.AssertionError
)もテストされていることが分かります。
JSDocから自動でランタイムAssertionを追加してくれるbabel-plugin-jsdoc-to-assertを使うことで、add.js
から型チェック的なassert
は取り除けます。
具体的にはpresets
にbabel-preset-jsdoc-to-assertを追加して、JSDocを書くだけです。
--- a/.babelrc
+++ b/.babelrc
@@ -5,6 +5,7 @@
"env": {
"development": {
"presets": [
+ "jsdoc-to-assert",
"power-assert"
]
}
diff --git a/src/add.js b/src/add.js
index 4748ae3..5cc3b9c 100644
--- a/src/add.js
+++ b/src/add.js
@@ -1,7 +1,9 @@
"use strict";
-const assert = require("assert");
+/**
+ * @param {number} x
+ * @param {number} y
+ * @returns {Number}
+ */
export default function add(x, y) {
- assert(typeof x === "number");
- assert(typeof y === "number");
return x + y;
-}
変更したコミット:
babel-preset-jsdoc-to-assertもpower-assert化できるといいのですが、Babelの変換の仕組み上難しいのでまだできてません。
また、assert
をアプリケーション側で使っていた際に、プロダクションビルドからは取り除きたいということがあります。その場合はunassertを使えば、取り除けるので便利です。
- twada/unassert: Encourage reliable programming by writing assertions in production code, and compiling them away from release
- twada/babel-plugin-unassert: Babel plugin to encourage reliable programming by writing assertions in production code, and compiling them away from release.
変更したコミット:
ここまでを全部適応した最終的な.bebelrc
は次のような形になっています。
{
"presets": [
"es2015"
],
"env": {
"development": {
"presets": [
"jsdoc-to-assert",
"power-assert"
]
},
"production": {
"plugins": [
"babel-plugin-unassert"
]
}
}
}
おわり
- espower-babelはBebel5以下における解だったので、役目はひとまず終わり
- 普通にコンパイル言語と同じように、デバッグ時のみ情報量の多いassertの適応、プロダクションビルド時は取り除くということができるようになった
- ホーア論理的な事前条件は
assert
やJSDocで、事後条件はテストで保証するみたいなことはやりやすくなった - コード量や実行速度はそこまで変わらずにチェックできることが色々増えた
お知らせ欄
JavaScript Primerの書籍版がAmazonで購入できます。
JavaScriptに関する最新情報は週一でJSer.infoを更新しています。
GitHub Sponsorsでの支援を募集しています。