# 帳票設計書 3-tap

## 概要

本ドキュメントは、Node.jsテストランナーの「TAPレポーター」の設計仕様を記載したものである。テスト結果をTAP（Test Anything Protocol）version 13形式で出力するレポーター機能の実装詳細、出力形式、処理フローについて説明する。

### 本帳票の処理概要

TAPレポーターは、Node.jsテストランナーが実行したテスト結果を、TAP（Test Anything Protocol）version 13準拠のテキストベースフォーマットで標準出力に表示する帳票である。TAPは多くのテストツールやCI/CDシステムで広くサポートされている標準的なテスト結果フォーマットであり、異なるツール間でのテスト結果の相互運用性を実現する。

**業務上の目的・背景**：テスト結果を標準化されたフォーマットで出力することで、様々なテストハーネス、CI/CDツール、レポート生成ツールとの連携を可能にする。TAP形式は1987年にPerlのテストフレームワーク用に開発されて以来、多くの言語やツールでサポートされており、テスト結果の機械可読性と人間可読性の両方を兼ね備えている。子プロセスでのテスト実行時のデフォルトレポーターとして使用される。

**帳票の利用シーン**：
- CI/CDパイプラインでのテスト結果収集
- テスト結果の自動解析・集計
- 複数テストツール間でのテスト結果の統合
- プロセス間通信でのテスト結果の受け渡し
- TAPパーサーを持つ外部ツールとの連携

**主要な出力内容**：
1. TAPバージョンヘッダー（TAP version 13）
2. テスト結果行（ok/not ok + テスト番号 + テスト名）
3. YAMLフォーマットの詳細情報（duration_ms, error等）
4. テストプラン（1..N形式）
5. サブテスト開始マーカー
6. 診断メッセージ（#プレフィックス）
7. コードカバレッジ情報

**帳票の出力タイミング**：
- テストランナー開始時にTAPバージョンヘッダーを出力
- 各テストの完了時にテスト結果を出力
- テストプラン情報の受信時にプラン行を出力
- 診断イベント発生時に即時出力

**帳票の利用者**：
- CI/CDシステム
- TAPパーサー/アナライザー
- テスト結果集計ツール
- ソフトウェア開発者（標準形式でのテスト結果が必要な場合）

## 帳票種別

テストレポート / TAP形式（Test Anything Protocol）

## 利用画面

| 画面No | 画面名 | URL/ルーティング | 出力操作 |
|--------|--------|-----------------|---------|
| - | CLIターミナル | `node --test --test-reporter=tap` | テスト実行コマンド |
| - | 子プロセス | NODE_TEST_CONTEXT=child | 自動選択（デフォルト） |

## 出力形式

### 基本仕様

| 項目 | 内容 |
|-----|------|
| ファイル形式 | テキスト（TAP version 13） |
| 用紙サイズ | N/A（コンソール出力） |
| 向き | N/A |
| ファイル名 | N/A（標準出力へ直接出力） |
| 出力方法 | 標準出力（stdout） |
| 文字コード | UTF-8 |

### TAP固有設定

| 項目 | 内容 |
|-----|------|
| TAPバージョン | 13 |
| インデント | 4スペース × ネストレベル |
| 詳細形式 | YAML（--- / ... で囲む） |
| エスケープ | # → \# , \ → \\\\ |

## 帳票レイアウト

### レイアウト概要

TAPレポーターは、TAPプロトコル仕様に準拠した形式でテスト結果を出力する。

```
┌─────────────────────────────────────┐
│          TAPヘッダー部              │
│  TAP version 13                    │
├─────────────────────────────────────┤
│          テスト結果部               │
│  # Subtest: test name              │
│  ok 1 - test name                  │
│    ---                             │
│    duration_ms: 10                 │
│    ...                             │
├─────────────────────────────────────┤
│          プラン部                   │
│  1..5                              │
├─────────────────────────────────────┤
│          カバレッジ部               │
│  # coverage report...              │
└─────────────────────────────────────┘
```

### TAPヘッダー部

| No | 項目名 | 説明 | データ取得元 | 表示形式 |
|----|-------|------|-------------|---------|
| 1 | バージョン | TAPプロトコルバージョン | 固定値(13) | `TAP version 13` |

### テスト結果部

| No | 項目名 | 説明 | データ取得元 | 表示形式 |
|----|-------|------|-------------|---------|
| 1 | サブテスト開始 | サブテストの開始 | test:start イベント | `# Subtest: {name}` |
| 2 | 成功結果 | テスト成功 | test:pass イベント | `ok {number} - {name}` |
| 3 | 失敗結果 | テスト失敗 | test:fail イベント | `not ok {number} - {name}` |
| 4 | SKIP指示 | スキップ理由 | skip属性 | `# SKIP {reason}` |
| 5 | TODO指示 | TODO理由 | todo属性 | `# TODO {reason}` |
| 6 | 期待失敗 | 期待される失敗 | expectFailure属性 | `# EXPECTED FAILURE` |
| 7 | YAML詳細 | 詳細情報 | details | YAML形式 |

### YAML詳細部

| No | 項目名 | 説明 | データ取得元 | 表示形式 |
|----|-------|------|-------------|---------|
| 1 | duration_ms | 実行時間 | details.duration_ms | 数値（ミリ秒） |
| 2 | type | テストタイプ | details.type | 文字列 |
| 3 | location | ファイル位置 | file:line:column | 文字列 |
| 4 | error | エラーメッセージ | error.message | 文字列/複数行 |
| 5 | code | エラーコード | error.code | 文字列 |
| 6 | name | エラー名 | error.name | 文字列 |
| 7 | expected | 期待値 | error.expected | 任意型 |
| 8 | actual | 実際値 | error.actual | 任意型 |
| 9 | operator | 比較演算子 | error.operator | 文字列 |
| 10 | stack | スタックトレース | error.stack | 複数行文字列 |

## 出力条件

### 抽出条件

| 条件名 | 説明 | 必須 |
|-------|------|-----|
| テスト結果イベント | test:pass/fail/start/plan/diagnostic/coverage | Yes |

### ソート順

| 優先度 | 項目 | 昇順/降順 |
|-------|------|---------|
| 1 | イベント発生順 | 昇順（時系列） |

### 改ページ条件

なし（連続出力）

## データベース参照仕様

### 参照テーブル一覧

本帳票はデータベースを参照しない。テストランナーからのイベントストリームを入力として使用する。

| テーブル名 | 用途 | 結合条件 |
|-----------|------|---------|
| N/A | - | - |

### イベントデータ構造

#### test:pass / test:fail イベント

| 参照項目 | 帳票項目との対応 | 取得条件 | 備考 |
|---------|----------------|---------|------|
| data.nesting | インデントレベル | - | 4スペース×nesting |
| data.testNumber | テスト番号 | - | `ok {testNumber}` |
| data.name | テスト名 | - | `- {name}` |
| data.skip | スキップ理由 | skipがある場合 | `# SKIP` |
| data.todo | TODO理由 | todoがある場合 | `# TODO` |
| data.expectFailure | 期待失敗 | expectFailureがある場合 | `# EXPECTED FAILURE` |
| data.details | 詳細情報 | - | YAML形式で出力 |
| data.file | ファイルパス | test:failの場合 | location用 |
| data.line | 行番号 | test:failの場合 | location用 |
| data.column | 列番号 | test:failの場合 | location用 |

#### test:plan イベント

| 参照項目 | 帳票項目との対応 | 取得条件 | 備考 |
|---------|----------------|---------|------|
| data.nesting | インデントレベル | - | - |
| data.count | テスト総数 | - | `1..{count}` |

## 計算仕様

### 計算項目一覧

| 項目名 | 計算式 | 端数処理 | 備考 |
|-------|-------|---------|------|
| インデント | `'    ' × nesting` | N/A | 4スペース × ネストレベル（メモ化） |
| location | `${file}:${line}:${column}` | N/A | 失敗テストのみ |

## 処理フロー

### 出力フロー

```mermaid
flowchart TD
    A[テストランナー開始] --> B[TAP version 13 出力]
    B --> C[イベント待機]
    C --> D{イベントタイプ判定}
    D -->|test:fail| E[not ok + 詳細出力]
    D -->|test:pass| F[ok + 詳細出力]
    D -->|test:plan| G[プラン出力 1..N]
    D -->|test:start| H[Subtest コメント出力]
    D -->|test:diagnostic| I[診断コメント出力]
    D -->|test:stdout/stderr| J[コメント出力]
    D -->|test:coverage| K[カバレッジレポート出力]
    E --> C
    F --> C
    G --> C
    H --> C
    I --> C
    J --> C
    K --> C
```

### reportTest関数処理

```mermaid
flowchart TD
    A[テスト結果受信] --> B[ステータス ok/not ok を設定]
    B --> C[テスト番号を追加]
    C --> D{名前あり?}
    D -->|Yes| E[名前を追加]
    D -->|No| F[skip判定]
    E --> F
    F -->|skip| G[# SKIP追加]
    F -->|todo| H[# TODO追加]
    F -->|expectFailure| I[# EXPECTED FAILURE追加]
    F -->|なし| J[改行追加]
    G --> J
    H --> J
    I --> J
    J --> K[結果を返却]
```

### jsToYaml関数処理

```mermaid
flowchart TD
    A[値を受信] --> B{undefined?}
    B -->|Yes| C[空文字返却]
    B -->|No| D{null?}
    D -->|Yes| E[~ を返却]
    D -->|No| F{オブジェクト?}
    F -->|No| G[単純値を返却]
    F -->|Yes| H{循環参照?}
    H -->|Yes| I[<Circular>を返却]
    H -->|No| J{Error?}
    J -->|Yes| K[エラー専用処理]
    J -->|No| L[オブジェクト再帰処理]
    K --> M[error,code,name,expected,actual,operator,stack出力]
```

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 表示メッセージ | 対処方法 |
|----------|---------|--------------|---------|
| ERR_TEST_FAILURE | テスト失敗時 | cause展開してエラー詳細を出力 | kUnwrapErrorsで展開対象を判定 |
| 循環参照 | オブジェクトに循環参照あり | `<Circular>` | seenセットで検出 |

## パフォーマンス要件

| 項目 | 内容 |
|-----|------|
| 想定データ件数 | 無制限（ストリーム処理） |
| 目標出力時間 | イベント受信後即時出力 |
| 同時出力数上限 | 1（シングルストリーム） |

## セキュリティ考慮事項

- テスト名、エラーメッセージに含まれる機密情報がそのまま出力される可能性がある
- スタックトレースにファイルパスが含まれ、ディレクトリ構造が露出する
- TAP形式は平文テキストのため、出力内容の暗号化は行われない

## 備考

- 子プロセス（NODE_TEST_CONTEXT=child）でのデフォルトレポーター
- TAP version 13はYAMLブロックをサポート
- tapEscape関数で特殊文字（#, \, 制御文字）をエスケープ
- SafeMapとSafeSetを使用してインデントのメモ化と循環参照検出を実装
- isAssertionLike関数でアサーションエラーを判定し、expected/actual/operatorを出力

---

## コードリーディングガイド

本帳票を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

TAPプロトコルの基本構造とエラーオブジェクトの構造を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | tap.js | `lib/internal/test_runner/reporter/tap.js` | kDefaultTAPVersion, kDefaultIndent定数 |
| 1-2 | test.js | `lib/internal/test_runner/test.js` | kUnwrapErrors定数の定義 |

**読解のコツ**: TAPプロトコルの仕様（version 13）を理解しておくと、コードの意図が明確になる。

#### Step 2: エントリーポイントを理解する

tapReporter非同期ジェネレータ関数の構造を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | tap.js | `lib/internal/test_runner/reporter/tap.js` | tapReporter関数全体（31-66行目） |

**主要処理フロー**:
1. **32行目**: `yield 'TAP version 13\n'` - TAPヘッダー出力
2. **33-65行目**: イベントループでの各タイプ処理
3. **35-39行目**: test:fail - reportTest + reportDetails（locationあり）
4. **40-43行目**: test:pass - reportTest + reportDetails
5. **44-46行目**: test:plan - プラン行出力
6. **47-49行目**: test:start - サブテストコメント出力
7. **50-57行目**: test:stderr/stdout - 各行をコメントとして出力
8. **58-60行目**: test:diagnostic - 診断コメント出力
9. **61-63行目**: test:coverage - getCoverageReport呼び出し

#### Step 3: テスト結果フォーマットを理解する

reportTest関数とreportDetails関数の実装を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | tap.js | `lib/internal/test_runner/reporter/tap.js` | reportTest関数（68-86行目） |
| 3-2 | tap.js | `lib/internal/test_runner/reporter/tap.js` | reportDetails関数（88-103行目） |

**主要処理フロー**:
- **68-86行目**: reportTest - ステータス行の構築（ok/not ok, SKIP/TODO等）
- **88-103行目**: reportDetails - YAMLブロックの出力

#### Step 4: YAML変換ロジックを理解する

jsToYaml関数の再帰的なオブジェクト→YAML変換を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | tap.js | `lib/internal/test_runner/reporter/tap.js` | jsToYaml関数（130-277行目） |
| 4-2 | tap.js | `lib/internal/test_runner/reporter/tap.js` | isAssertionLike関数（279-281行目） |

**主要処理フロー**:
- **130-159行目**: プリミティブ値の処理
- **160-189行目**: オブジェクトの再帰処理
- **191-273行目**: Errorオブジェクトの特別処理（cause展開、アサーション情報）

#### Step 5: ユーティリティ関数を理解する

インデント計算とエスケープ処理を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | tap.js | `lib/internal/test_runner/reporter/tap.js` | indent関数（105-114行目） |
| 5-2 | tap.js | `lib/internal/test_runner/reporter/tap.js` | tapEscape関数（117-128行目） |

**主要処理フロー**:
- **105-114行目**: indent - SafeMapを使用したメモ化インデント
- **117-128行目**: tapEscape - 制御文字と#, \のエスケープ

### プログラム呼び出し階層図

```
node --test --test-reporter=tap
    │
    ├─ lib/internal/test_runner/runner.js
    │      └─ parseCommandLine() → getReportersMap()
    │              └─ require('internal/test_runner/reporter/tap')
    │
    └─ lib/internal/test_runner/reporter/tap.js
           │
           ├─ tapReporter (async generator)
           │      ├─ reportTest()
           │      ├─ reportDetails()
           │      │      └─ jsToYaml() [再帰]
           │      │             └─ isAssertionLike()
           │      ├─ indent() [メモ化]
           │      ├─ tapEscape()
           │      └─ getCoverageReport()
           │             └─ lib/internal/test_runner/utils.js
           │
           └─ lazyLoadTest()
                  └─ lib/internal/test_runner/test.js (kUnwrapErrors)
```

### データフロー図

```
[入力]                    [処理]                           [出力]

テストイベント ───▶ tapReporter ジェネレータ ───▶ 標準出力(stdout)
  ストリーム              │
                         ├─ 開始 → "TAP version 13\n"
                         ├─ test:pass → reportTest(ok) + reportDetails
                         ├─ test:fail → reportTest(not ok) + reportDetails
                         ├─ test:plan → "1..{count}\n"
                         ├─ test:start → "# Subtest: {name}\n"
                         ├─ test:diagnostic → "# {message}\n"
                         └─ test:coverage → getCoverageReport()
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| tap.js | `lib/internal/test_runner/reporter/tap.js` | ソース | TAPレポーターのメイン実装 |
| utils.js | `lib/internal/test_runner/utils.js` | ソース | getCoverageReport関数、kBuiltinReporters |
| test.js | `lib/internal/test_runner/test.js` | ソース | kUnwrapErrors定数 |
| errors.js | `lib/internal/errors.js` | ソース | inspectWithNoCustomRetry関数 |
| util.js | `lib/internal/util.js` | ソース | isError関数、kEmptyObject |
| runner.js | `lib/internal/test_runner/runner.js` | ソース | テストランナーのメイン処理 |
