30歳からのプログラミング

30歳無職から独学でプログラミングを開始した人間の記録。

Power Assert Deno を作った

Deno で Power Assert を利用するためのモジュールを作った。

github.com

Power Assert とは、テスト失敗時に詳細な情報を表示する仕組みのこと。

例えば以下のようなテストコードがあるとき、通常はx()y.zのそれぞれの結果と、それらが一致しないことのみが、表示される。

Deno.test("hello world", () => {
  const x = () => "Hello World 🦖"
  const y = {z: "Hello World 🦕"};
  assertEquals(x(), y.z);
});
-   "\"Hello World 🦖\""
+   "\"Hello World 🦕\""

だが Power Assert を利用すると、以下のようなより詳細な情報を得られるようになる。

assertEquals(x(), y.z);
             |    | |
             |    | "Hello World 🦕"
             |    Object{z:"Hello World 🦕"}
             "Hello World 🦖"

使用法

以下のコマンドで、インストールできる。

$ deno install -n assert -f --allow-read --allow-write --allow-run --unstable https://deno.land/x/power_assert_deno@0.1.0/cli.ts

-nフラグで指定した名前でインストールされるので、この場合、assertというコマンド名になる。

そして、標準のアサーション関数の代わりに、専用のアサーション関数を使うようにする。

- import { assert, assertEquals } from "https://deno.land/std/testing/asserts.ts";
+ import { assert, assertEquals } from "https://deno.land/x/power_assert_deno@0.1.0/mod.ts";

あとは、先程インストールしたassertコマンドを使えばよい。
src/ディレクトリにあるテストを実行するには、以下のようにすればよい。

$ assert src/

開発について

Power Assert という概念は特定の言語に特有のものではなく、様々な言語で実装されている。
私が Power Assert を知ったのは Node.js 版。JavaScript や TypeScript で何か作るときは基本的に入れるようにしているくらい好きなライブラリで、これを Deno でも使いたいというのが、そもそもの開発の動機。

また、Deno の習作の題材として丁度よさそう、という理由もあった。自分で一から考えるのに比べて、難易度が低い。
「何を作るべきか」というゴールが既に見えているし、実装を参考にすることもできる。
実際に、Node.js 版のソースコードに目を通し、ひたすらプリントデバッグして処理の流れを把握しながら、開発を進めていった。
一部の処理についてはコードをほぼ流用しているし、提供されている npm パッケージをそのままimportして使っている箇所もある(stringifier)。

課題点

いくつか、解決したくてもできなかった課題がある。これらを解決できれば、もっと実用性のあるモジュールになれる気がしている。
よい解決策を知っている方がいれば教えてください。

変換後のコードを一時ファイルとして書き出している

オリジナルのテストコードを Power Assert を使ったコードに変換して、その変換後のコードに対してテストを実行する。
そして Power Assert Deno では、変換後のコードを実際に書き出し、それに対してdeno testを実行する形になっている。
だが Node.js 版では恐らく、変換後のコードを書き出してはおらず、メモリ上で処理を終えているように見える。

espower-loaderのこの部分がその処理を行っていると思うのだが、require.extensionsという機能を使って、オリジナルのテストコードではなく変換後のコードを読み込ませている。
ドキュメントによればrequire.extensionsは既にdeprecatedのようではあるが。

nodejs.org

この処理を Deno で行うにはどうすればいいのか、そもそもそういった機能が用意されているのか、分からない。
だがいちいちファイルを書き出しそれを削除するのは無駄だし、インストール時に--allow-write権限を付けなければいけない原因にもなってしまっている。
できれば改善したい。

また、一時ファイルとして書き出す関係上、import文のパスを書き換えるという作業も発生している。import SomeModule from "./some.ts"というコードがある場合、./some.tsを解決できなくなるので、/User/numb/my-pj/some.tsのような絶対パスに書き換えて対応している。
そしてそれに関係して、Dynamic Import への対応が不完全なものになっている。import("./some.ts")ように文字列リテラルを渡されれば大丈夫なのだが、import(x)のように変数や式を渡されると、エラーになってしまう。
これ自体は頑張れば対応できる気もするが、一時ファイルとして書き出す仕組みそのものを改善できれば、そもそもパスの書き換え自体が不要になる。

行数を表示できない

Node.js 版では、ファイル名の後ろに、失敗したアサーション関数の行数が表示される
だが Power Assert Deno ではこれができていない。
というのも、まず TypeScript コードをトランスパイルして JavaScript コードに変換しているのだが、その際に改行などが行われ、オリジナルのテストコードの情報が失われてしまうのである。
変換後の JavaScript コードを AST に変換しているので、そこで得られる位置情報はオリジナルのものとは乖離してしまっている。
トランスパイル時にAAAA,OAAO,EACW,EAAE;AAClB,WAAW,EAAE,EAAE;...のような内容のソースマップが得られるのだが、これをどう活用すればいいのかも分からない。

アサーション関数のインポート元を書き換えないといけない

「使用法」のところで説明したように、"https://deno.land/x/power_assert_deno@0.1.0/mod.ts"からアサーション関数をインポートする形に書き換えないといけない。
依存モジュールの管理を一箇所で行っていればその部分を書き換えるだけで済むかもしれないが、テストコードには手を加えることなく利用できるのが理想的ではある。