denoでMarkdown内コードのテストをしてみる（ちょっといい感じに）

# はじめに

モジュールを作成する際、使いやすいようにREADMEにコードブロックで**使用例**などを載せることが多いと思います。
しかしその使用例、**きちんと動きますか？**

コードブロックのコードはべた書きが多いので、タイポがあったり、モジュールに変更があった場合、更新し忘れることが多々あります。

**「使用例が動かない」** ということは、せっかくあなたが作ったモジュールの評価が下がることにも繋がりかねません。

ということで、`deno test`コマンドを駆使して、Markdownのコードをテストできるようにしましょう！
（※型チェックのみ）

<!--
# `deno test`

denoには標準でテストツールが入っていて、コード内で`Deno.test`を使うことで簡単にテストコードを作成することが可能です。

```ts
Deno.test("test case", () => {
    // テストしたいコード
});
```

`deno test`コマンドには様々なオプションが指定でき、その中に`--doc`というものがあります。
これを今回は使用します。
-->

# テスト方法

本記事では以下のコードをテストします。
```ts:mod.ts
export function add(a: number, b: number) {
  return a + b;
}
```

````md:README.md
# Usage

```ts
import { add } from "./mod.ts";

const a = add(1, 2);
console.log(a);
```
````

##  `deno test --doc`

```console
$ deno test --help
deno-test 
~~~~~~~~~~ 省略 ~~~~~~~~~~
        --doc
            UNSTABLE: type-check code blocks
~~~~~~~~~~ 省略 ~~~~~~~~~~
```

コードブロックの型チェックを行ってくれます。これを使うことで、例のコードをチェックすると、


```
running 0 tests from ./README.md
running 0 tests from ./mod.ts

ok | 0 passed | 0 failed (47ms)
```

`0 tests` や `0 passed`とはなっていますが、きちんとチェックできています。

証拠に、import文のところを`./hoge.ts`としてみると、

```
error: Module not found "file:///C:/test-doc/hoge.ts".
    at file:///C:/test-doc/README.md$3-9.ts:1:21
```

とエラーになります。

# 改善してみよう！

前章でコードブロックの型チェックをすることは"一応"できました。
しかし、例のREADME、不親切ではありませんか？
`import`のURLが相対パスになっています。使う側からしたらコピペするだけでは使えないので不便です。

使いやすいようにURLインポートにしてあげましょう。
````md:README.md
# Usage

```ts
import { add } from "https://example.com/mod.ts";

const a = add(1, 2);
console.log(a);
```
````
こうなります。`example.com`は適当な配布先になりますね。deno 3rd Partyモジュールなら`deno.land/x/(モジュール名)` とかでしょうか。

これで先ほどのテストを行うと...

```
error: Module not found "https://example.com/mod.ts".
    at file:///C:/test-doc/README.md$3-9.ts:1:21
```

という風に、`https://example.com/mod.ts`がないって怒られます。

この問題を[Import Maps](https://deno.land/manual@v1.28.3/basics/modules/import_maps)で解決しましょう！

`test-import-map.json`に以下のように設定します。

```json:test-import-map.json
{
  "imports": {
    "https://example.com/": "./"
  }
}
```

そして、テスト時のコマンドを次のようにして、Import Mapsを指定してあげます。

```console
$ deno test --doc --import-map ./test-import-map.json
```

すると、

```
running 0 tests from ./README.md
running 0 tests from ./mod.ts

ok | 0 passed | 0 failed (46ms)
```

テストが成功しました🎊

# `deno.json`を使う

テストするたびに毎回、上のようなコマンドを打つのは面倒くさいので、タスクに登録します。

```json:deno.json
{
  "tasks": {
    "test": "deno test --doc --import-map ./test-import-map.json"
  }
}
```

実行するときには、

```console
$ deno task test
```

これだけです！

# 問題点（懸念点）

いくつか考慮しておくべき点があるので書いておきます。

- `.md`以外のファイルにもImport Mapsが適用されてしまう。
    - チェックしたいファイル毎にテストコマンドを用意する必要あり。
    - `deno test --doc --import-map test-import-map.json README.md` のようにファイルごとに実行
    - deno_task_shellの[issue](https://github.com/denoland/deno_task_shell/issues/6)にglob対応したいと書いてあるのでアップデートに期待
- 既にImportMapを使用している場合に実行用とテスト用の2つのファイルを管理する必要がある
    - 2つのファイル間で整合性を保つのが大変
- `import-map.json`をテスト用に作る必要がある。
    - deno.json内にも書けるがそこに書くと通常実行時にも読み込んでしまう。
    - 本記事では`test-import-map.json`とはしたものの、もっとスマートな名前が決めれるといい。`test`ディレクトリ内とかに作ってもいいかも
- 実行はしてくれない
    - あくまでも`--doc`オプションは型チェックだけなので実行はしてくれない。


# さいごに

`--doc`について書かれている記事はいくつかあったものの、importのURLが相対パスになってしまうのが嫌いで避けていました。
なんとかならないかなと考えた結果Import Mapを使用する方法に行きつきました。
もっとスマートな方法あるよ！って方いたら遠慮なく教えてください🙇

Denoアドベントカレンダー、参加3年目。今年は忙しくて書けないかなぁと思いながらも、軽めの題が見つかったので課題の合間に急いで書いてます笑

大分早いですが、**メリークリスマス & よいお年**を！