🧪

Typescript で Document Testing したい

2024/03/15に公開

TypeScriptを使用している開発者であれば、コードにドキュメントを書いた人もいるかと思います。
ドキュメントに対して、ドキュメントテストというのが書けるらしいです。
この記事では、TypeScriptでドキュメントテストを行うためのツールをいくつか紹介します。

サンプルコードは、以下にあります。

https://github.com/silverbirder/playground

Document Testingとは?

ドキュメントテストとは、JSDocやTSDocのコメントを利用して、コード上にドキュメントを記述し、
そのドキュメント内のコードサンプルを実際にテストすることです。
このアプローチにより、ドキュメントが常に最新の状態に保たれ、コードの使用方法が正確に反映されるようになります。
ただし、JavaScriptやTypeScript自体にはこの機能が組み込まれていないため、外部のライブラリを使用する必要があります。
Deno では公式に Documentation Tests が用意されているようです。素晴らしいですね。

試したツール

私が試したツールは以下の3つです。

  • doctest-ts
    • コード内のコメントからテストコードを生成し、Jestを使用して実行します。
      • Jest以外のテストツールも選択可能です
  • tsdoc-testify
    • TSDocコメントからテストコードを生成し、Nodejsのassertモジュールでテストを実行します。
  • the-real-doctest
    • TSDocコメント内で直接テストを記述し、独自のコマンドでテストを実行します。

また、doc-vitestも魅力的に見えましたが、今回は検証から除外しました。

各ツールの使い方

doctest-ts

doctest-tsは、特定のフォーマットでコメントにテストケースを記述することで、テストを簡単に追加できます。
ドキュメントは、以下のように書きます。

// src/hasFoo.ts
/** Does this string contain foo, ignoring case?

    hasFoo('___foo__') // => true
    hasFoo('   fOO  ') // => true
    hasFoo('Foo.') // => true
    hasFoo('bar') // => false
    hasFoo('fo') // => false
    hasFoo('oo') // => false

*/
function hasFoo(s: string): boolean {
  return null != s.match(/foo/i);
}

以下のコマンドを使用してテストファイルを生成し、Jestでテストを実行できます。

doctest-ts --jest src/hasFoo.ts

生成されたテストコードは以下です。

/** Does this string contain foo, ignoring case?

    hasFoo('___foo__') // => true
    hasFoo('   fOO  ') // => true
    hasFoo('Foo.') // => true
    hasFoo('bar') // => false
    hasFoo('fo') // => false
    hasFoo('oo') // => false

*/
function hasFoo(s: string): boolean {
  return null != s.match(/foo/i);
}

import "jest"
const __expect: jest.Expect = expect

    describe("hasFoo", () => {
      it("hasFoo", () => {
        __expect(hasFoo("___foo__")).toEqual(true)
        __expect(hasFoo("   fOO  ")).toEqual(true)
        __expect(hasFoo("Foo.")).toEqual(true)
        __expect(hasFoo("bar")).toEqual(false)
        __expect(hasFoo("fo")).toEqual(false)
        __expect(hasFoo("oo")).toEqual(false)})
    })

tsdoc-testify

tsdoc-testifyは、TSDocコメント内にテストケースを記述し、Nodeのassertモジュールを使用してテストを実行します。
ドキュメントは、以下のように書きます。

// ./src/sub.ts
/**
 * sub function
 *
 * @remarks
 * demo
 *
 * @example
 *
 * ```
 * import * as assert from "assert";
 * import { sub } from "./sub";
 *
 * assert.equal(sub(2, 1), 1);
 * ```
 *
 * @example
 *
 * ```
 * import * as assert from "assert";
 * import { sub } from "./sub";
 *
 * assert.equal(sub(4, 5), -1);
 * ```
 * @param a
 * @param b
 */
export function sub(a: number, b: number) {
  return a - b;
}

テストケースは以下のコマンドで生成されます。

tsdoc-testify --filepath ./src/sub.ts

生成されたテストコードは以下です。

// Code generated by "tsdoc-testify"; DO NOT EDIT.

import * as assert from "assert";
import { sub } from "./sub";
test("/<path_to_app>/src/sub.ts_0", () => {
  assert.equal(sub(2, 1), 1);
});
test("/<path_to_app>/src/sub.ts_1", () => {
  assert.equal(sub(4, 5), -1);
});

the-real-doctest

the-real-doctestは、TSDocコメント内で直接テストを記述し、独自のコマンドを使用してテストを実行します。
これはテストコードの生成を省略し、シンプルな比較演算子(==, !=, === or !==)を使用してテストを記述できます。
ドキュメントは、以下のように書きます。

// ./src/nsum.ts
/**
 * @param n
 * @returns the sum of the n first integers
 * @example
 * const n = 5
 * const expected = 1 + 2 + 3 + 4 + 5
 * const actual = nsum(n)
 * actual == expected
 * @example nsum(3) == 1 + 2 + 3
 * @example nsum(8) == 36 // This should fail
 */
function nsum(n: number): number {
  return (n * (n + 1)) / 2;
}

以下のコマンドで、テストを実行できます。

the-real-doctest test ./src/nsum.ts

結論

ドキュメントテストは、コードの使用方法を明確に理解し、ドキュメントを最新の状態に保つのに非常に役立ちます。
開発エディタのコード補完を使えば、コード内部を読まなくても使い方の理解が深まります。
個人的には、Denoのドキュメントテスト機能が最も興味深いと感じましたが、選択はプロジェクトのニーズや好みによって異なるでしょう。
ドキュメントテストを積極的に利用して、より良いコードベースを目指しましょう!

Discussion