# 帳票設計書 1-JUnit XMLレポート

## 概要

本ドキュメントは、Bunテストランナーが出力するJUnit XML形式のテスト結果レポートの設計仕様を記載したものである。

### 本帳票の処理概要

JUnit XMLレポートは、テスト実行結果を標準的なJUnit XML形式でファイル出力する帳票である。CI/CDパイプラインやテスト管理ツールとの連携を主目的として設計されている。

**業務上の目的・背景**：継続的インテグレーション（CI）環境において、テスト結果を標準フォーマットで記録・共有する必要がある。JUnit XML形式は業界標準として広く採用されており、Jenkins、GitHub Actions、GitLab CI、CircleCIなど主要なCI/CDツールが直接解析可能である。この帳票により、テスト結果の可視化、傾向分析、品質ゲートの自動判定が可能となる。

**帳票の利用シーン**：テストスイート実行後のCI/CDパイプラインでの品質レポート生成、テスト管理ツールへのインポート、テスト実行履歴の蓄積と傾向分析、PRレビュー時のテスト結果確認に利用される。

**主要な出力内容**：
1. テストスイート全体のサマリ情報（総テスト数、失敗数、スキップ数、実行時間）
2. 各テストケースの詳細（名前、クラス名、実行時間、ファイル位置、行番号）
3. 失敗・スキップ・TODOテストの状態と理由
4. CI環境情報（GitHub Actions/GitLab CI連携時のコミットSHA、ジョブURL）

**帳票の出力タイミング**：`bun test`コマンド実行完了時に、`--reporter=junit`オプション指定時または`bunfig.toml`の`test.reporter.junit`設定時に出力される。

**帳票の利用者**：開発者、QAエンジニア、DevOpsエンジニア、CI/CDパイプライン管理者

## 帳票種別

テスト結果レポート / XML形式

## 利用画面

| 画面No | 画面名 | URL/ルーティング | 出力操作 |
|--------|--------|-----------------|---------|
| CLI-001 | コマンドライン | `bun test --reporter=junit` | コマンド実行完了時に自動出力 |

## 出力形式

### 基本仕様

| 項目 | 内容 |
|-----|------|
| ファイル形式 | XML（JUnit形式準拠） |
| 用紙サイズ | N/A（電子ファイル） |
| 向き | N/A |
| ファイル名 | `bunfig.toml`の`test.reporter.junit`で指定、またはCLIの`--reporter-outfile`で指定 |
| 出力方法 | ファイル書き込み |
| 文字コード | UTF-8 |

### XML固有設定

| 項目 | 内容 |
|-----|------|
| XMLバージョン | 1.0 |
| XMLエンコーディング | UTF-8 |
| ルート要素 | `<testsuites>` |

## 帳票レイアウト

### レイアウト概要

JUnit XML形式の階層構造に従い、テストスイート（ファイル単位）とテストケース（個別テスト単位）の入れ子構造を持つ。

```
<?xml version="1.0" encoding="UTF-8"?>
<testsuites name="bun test" tests="N" assertions="N" failures="N" skipped="N" time="N">
  <testsuite name="filename" file="path" tests="N" assertions="N" failures="N" skipped="N" time="N" hostname="H">
    <properties>
      <property name="ci" value="URL" />
      <property name="commit" value="SHA" />
    </properties>
    <testcase name="test name" classname="describe scope" file="path" line="N" time="N" assertions="N">
      <failure type="AssertionError" />  <!-- 失敗時のみ -->
      <skipped />  <!-- スキップ時のみ -->
    </testcase>
  </testsuite>
</testsuites>
```

### ルート要素（testsuites）

| No | 項目名 | 説明 | データ取得元 | 表示形式 |
|----|-------|------|-------------|---------|
| 1 | name | テストランナー名 | 固定値 "bun test" | 文字列 |
| 2 | tests | 総テストケース数 | total_metrics.test_cases | 整数 |
| 3 | assertions | 総アサーション数 | total_metrics.assertions | 整数 |
| 4 | failures | 失敗テスト数 | total_metrics.failures | 整数 |
| 5 | skipped | スキップテスト数 | total_metrics.skipped | 整数 |
| 6 | time | 総実行時間 | 開始時刻からの経過時間（秒） | 小数 |

### testsuite要素

| No | 項目名 | 説明 | データ取得元 | 表示形式 |
|----|-------|------|-------------|---------|
| 1 | name | テストファイル名またはdescribeブロック名 | SuiteInfo.name | 文字列 |
| 2 | file | ファイルパス | テストファイルの相対パス | 文字列 |
| 3 | line | 行番号（describeブロックの場合） | SuiteInfo.line_number | 整数 |
| 4 | tests | テストケース数 | suite_info.metrics.test_cases | 整数 |
| 5 | assertions | アサーション数 | suite_info.metrics.assertions | 整数 |
| 6 | failures | 失敗数 | suite_info.metrics.failures | 整数 |
| 7 | skipped | スキップ数 | suite_info.metrics.skipped | 整数 |
| 8 | time | 実行時間 | suite_info.metrics.elapsed_time（秒） | 小数 |
| 9 | hostname | ホスト名 | gethostname()システムコール | 文字列 |

### testcase要素

| No | 項目名 | 説明 | データ取得元 | 表示形式 |
|----|-------|------|-------------|---------|
| 1 | name | テスト名 | test_entry.base.name | 文字列 |
| 2 | classname | 親スコープ名（">" で連結） | describeスコープの階層 | 文字列 |
| 3 | file | ファイルパス | テストファイルの相対パス | 文字列 |
| 4 | line | 行番号 | test_entry.base.line_no | 整数 |
| 5 | time | 実行時間 | elapsed_ns / 1e9（秒） | 小数（6桁精度） |
| 6 | assertions | アサーション数 | sequence.expect_call_count | 整数 |

### failure要素

| No | 項目名 | 説明 | データ取得元 | 表示形式 |
|----|-------|------|-------------|---------|
| 1 | type | エラー種別 | 失敗理由に応じた固定値 | "AssertionError" / "TimeoutError" |
| 2 | message | エラーメッセージ | 失敗理由に応じた固定メッセージ | 文字列 |

## 出力条件

### 抽出条件

| 条件名 | 説明 | 必須 |
|-------|------|-----|
| --reporter=junit | CLIオプションでjunitレポーターを指定 | No |
| bunfig.toml test.reporter.junit | 設定ファイルでjunit出力パスを指定 | No |
| テスト実行完了 | テストスイートの実行が完了していること | Yes |

### ソート順

| 優先度 | 項目 | 昇順/降順 |
|-------|------|---------|
| 1 | テストファイルの実行順 | 実行順 |
| 2 | テストケースの定義順 | 定義順 |

### 改ページ条件

N/A（XML形式のため改ページなし）

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

### 参照テーブル一覧

N/A（データベースを使用しない。メモリ上のテスト実行結果を直接参照）

### データ構造

#### JunitReporter構造体

| フィールド | 型 | 用途 |
|-----------|------|------|
| contents | ArrayListUnmanaged(u8) | XML出力バッファ |
| total_metrics | Metrics | 全体集計情報 |
| testcases_metrics | Metrics | テストケース集計情報 |
| suite_stack | ArrayListUnmanaged(SuiteInfo) | ネストしたsuiteのスタック |
| current_depth | u32 | 現在のネスト深度 |
| hostname_value | ?string | キャッシュされたホスト名 |
| properties_list_to_repeat_in_every_test_suite | ?[]u8 | CI情報のpropertiesリスト |

#### Metrics構造体

| フィールド | 型 | 用途 |
|-----------|------|------|
| test_cases | u32 | テストケース数 |
| assertions | u32 | アサーション数 |
| failures | u32 | 失敗数 |
| skipped | u32 | スキップ数 |
| elapsed_time | u64 | 経過時間（ミリ秒） |

## 計算仕様

### 計算項目一覧

| 項目名 | 計算式 | 端数処理 | 備考 |
|-------|-------|---------|------|
| 実行時間（秒） | elapsed_time_ms / 1000.0 | 6桁精度 | ミリ秒から秒への変換 |
| testcase実行時間 | elapsed_ns / 1e9 | 6桁精度 | ナノ秒から秒への変換 |

## 処理フロー

### 出力フロー

```mermaid
flowchart TD
    A[テスト実行開始] --> B[JunitReporter.init]
    B --> C{テストスイート開始}
    C --> D[beginTestSuite: XMLヘッダー・testsuite開始タグ出力]
    D --> E{テストケース実行}
    E --> F[writeTestCase: testcase要素出力]
    F --> G{次のテスト?}
    G -->|Yes| E
    G -->|No| H{ネストしたdescribe終了?}
    H -->|Yes| I[endTestSuite: testsuite終了タグ・メトリクス集計]
    I --> G
    H -->|No| J{次のファイル?}
    J -->|Yes| C
    J -->|No| K[writeToFile: testsuites終了・ファイル書き込み]
    K --> L[終了]
```

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 表示メッセージ | 対処方法 |
|----------|---------|--------------|---------|
| ファイル書き込み失敗 | 出力先ディレクトリが存在しない、権限不足 | "Failed to write JUnit report to {path}" | 出力先ディレクトリの作成・権限確認 |
| ファイルオープン失敗 | ファイルパスが無効 | "Failed to write JUnit report to {path}" | パス設定の確認 |
| メモリ不足 | 大量のテストケースでバッファ確保失敗 | OOM処理 | テスト分割または環境リソース増強 |

## パフォーマンス要件

| 項目 | 内容 |
|-----|------|
| 想定データ件数 | 数千テストケース |
| 目標出力時間 | テスト実行時間の1%未満 |
| 同時出力数上限 | 1（シングルスレッド出力） |

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

- テストファイルパスや名前に含まれる特殊文字（`<`, `>`, `&`, `"`, `'`）はXMLエスケープ処理
- 制御文字（0x00-0x1F）は数値参照形式（`&#N;`）でエスケープ
- 出力ファイルパスはユーザー指定のため、ディレクトリトラバーサルに注意が必要

## 備考

- JUnit XML形式はJUnit 4/5互換
- ネストしたdescribeブロックはネストしたtestsuite要素として出力
- CI環境変数（GITHUB_RUN_ID、CI_JOB_URL等）が設定されている場合、properties要素としてCI情報を付加
- Windowsではhostnameの取得はスキップされる

---

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

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

### 推奨読解順序

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

JunitReporter構造体とその関連型を理解することが重要。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | test_command.zig | `src/cli/test_command.zig` | JunitReporter構造体（73-171行目）、Metrics構造体（129-142行目）、SuiteInfo構造体（115-127行目）の定義を確認 |

**読解のコツ**: Zigの構造体はフィールド定義とメソッドが同一ブロック内に記述される。`pub fn`で始まるものがパブリックメソッド。

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

テストコマンド実行からJunitReporter初期化までの流れを確認。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | test_command.zig | `src/cli/test_command.zig` | TestCommand.exec関数内のreporter初期化（1336-1380行目） |
| 2-2 | bunfig.zig | `src/bunfig.zig` | test.reporter.junitの設定読み込み（288-296行目） |

**主要処理フロー**:
1. **1369行目**: `ctx.test_options.reporters.junit`フラグをチェック
2. **1370行目**: `JunitReporter.init()`でインスタンス生成
3. **1371行目**: `reporter.reporters.junit`に割り当て

#### Step 3: XML生成処理を理解する

JunitReporterの各メソッドがどのようにXMLを構築するかを確認。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | test_command.zig | `src/cli/test_command.zig` | beginTestSuite（266-329行目）- XMLヘッダーとtestsuite開始タグの生成 |
| 3-2 | test_command.zig | `src/cli/test_command.zig` | writeTestCase（372-518行目）- testcase要素の生成、状態に応じたfailure/skipped要素の追加 |
| 3-3 | test_command.zig | `src/cli/test_command.zig` | endTestSuite（331-370行目）- メトリクス集計と閉じタグ |
| 3-4 | test_command.zig | `src/cli/test_command.zig` | writeToFile（520-566行目）- 最終的なファイル出力 |

**主要処理フロー**:
- **271-279行目**: XMLヘッダー `<?xml version="1.0" encoding="UTF-8"?>` と `<testsuites>` 開始タグ
- **283-304行目**: `<testsuite>` 要素の属性（name, file, line）生成
- **394-412行目**: `<testcase>` 要素の基本属性生成
- **414-517行目**: テスト結果に応じたfailure/skipped子要素の生成

#### Step 4: テスト完了時のコールバックを理解する

テスト完了時にどのようにJunitReporterが呼び出されるかを確認。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | test_command.zig | `src/cli/test_command.zig` | handleTestCompleted（867-953行目）- テスト完了時のコールバック |
| 4-2 | test_command.zig | `src/cli/test_command.zig` | maybePrintJunitLine（721-861行目）- JUnit出力の条件分岐とwriteTestCase呼び出し |

**主要処理フロー**:
- **728-729行目**: junitレポーターの存在確認
- **754-764行目**: ファイル変更時のtestsuite切り替え
- **832-836行目**: ネストしたdescribeブロックのtestsuite生成
- **857行目**: writeTestCase呼び出し

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

```
TestCommand.exec (1307行目)
    │
    ├─ JunitReporter.init (1370行目)
    │
    ├─ runAllTests (1541行目)
    │      │
    │      └─ handleTestCompleted (867行目)
    │             │
    │             └─ maybePrintJunitLine (721行目)
    │                    │
    │                    ├─ JunitReporter.beginTestSuite (266行目)
    │                    ├─ JunitReporter.endTestSuite (331行目)
    │                    └─ JunitReporter.writeTestCase (372行目)
    │
    └─ JunitReporter.writeToFile (520行目)
```

### データフロー図

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

テスト定義          ─────▶ JunitReporter                ─────▶ JUnit XML
(test/describe)            ├─ beginTestSuite                   ファイル
                           ├─ writeTestCase
テスト実行結果      ─────▶  └─ endTestSuite             ─────▶
(pass/fail/skip)
                           writeToFile                  ─────▶
bunfig.toml設定     ─────▶ (junit出力パス指定)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| test_command.zig | `src/cli/test_command.zig` | ソース | JunitReporter実装、テストコマンドのエントリーポイント |
| bunfig.zig | `src/bunfig.zig` | ソース | bunfig.tomlのtest.reporter.junit設定パース |
| Arguments.zig | `src/cli/Arguments.zig` | ソース | --reporter, --reporter-outfileのCLI引数定義 |
