# 帳票設計書 8-テスト結果サマリーレポート

## 概要

本ドキュメントは、Julia の `Test.print_test_results()` によって出力されるテスト結果サマリーレポートの設計仕様を記述する。

### 本帳票の処理概要

本帳票は、Julia の `Test` 標準ライブラリにおいて、テストセットの実行結果（Pass/Fail/Error/Broken/Total/実行時間）を表形式で標準出力にレポートする。`@testset` マクロの終了時（`finish` 関数の呼び出し時）に自動的に出力される。ネストされたテストセットの結果を階層的に表示する。

**業務上の目的・背景**：Julia プログラムのテスト結果を一覧形式で確認するために、テストセット全体の成功・失敗状況を素早く把握できるレポートが必要である。テストセットが階層的にネストされている場合でも、各レベルの結果を視覚的に整理して表示することで、問題箇所の迅速な特定を支援する。

**帳票の利用シーン**：`@testset` ブロックの実行後、テスト結果の概要を確認する際に使用する。CI/CD パイプラインでのテスト実行結果の確認や、開発中のテスト実行結果の確認に日常的に利用される。

**主要な出力内容**：
1. Test Summary ヘッダー行（Pass / Fail / Error / Broken / Total / Time）
2. テストセット名と各カテゴリの件数（階層的にインデント表示）
3. 色付きの結果表示（Pass=緑、Fail/Error=赤、Broken=黄）
4. 実行時間（秒またはm:ss形式）
5. 失敗時のRNG（乱数生成器）情報

**帳票の出力タイミング**：最上位の `@testset` ブロックが `finish` される時（テストセットの深度が0に戻った時）。

**帳票の利用者**：Julia プログラムの開発者、テストエンジニア、CI/CD パイプライン。

## 帳票種別

集計表（テスト結果の集計レポート）

## 利用画面

| 画面No | 画面名 | URL/ルーティング | 出力操作 |
|--------|--------|-----------------|---------|
| - | Julia REPL | julia> | `@testset` ブロックの実行完了時に自動出力 |
| - | CI/CD | - | テストスクリプト実行時 |

## 出力形式

### 基本仕様

| 項目 | 内容 |
|-----|------|
| ファイル形式 | テキスト（標準出力） |
| 用紙サイズ | N/A |
| 向き | N/A |
| ファイル名 | N/A |
| 出力方法 | 標準出力（stdout）への複数行出力 |
| 文字コード | UTF-8 |

### テキスト出力固有設定

| 項目 | 内容 |
|-----|------|
| 色付き出力 | Pass=緑、Fail/Error=赤（error_color）、Broken=黄（warn_color）、Total=青（info_color） |
| 太字 | ヘッダー行は太字 |
| インデント | ネストされたテストセットは2スペースずつインデント |

## 帳票レイアウト

### レイアウト概要

テスト結果サマリーは、ヘッダー行、テストセット行（階層的）で構成される表形式のレポートである。

```
┌──────────────────────────────────────────────────────────────────┐
│  Test Summary:          | Pass  Fail  Error  Broken  Total  Time │  ← ヘッダー行（太字）
│  My Test Suite          |  100     2      1       3    106  5.2s │  ← ルートテストセット
│    Sub Test A           |   50     1      0       1     52  2.1s │  ← 子テストセット
│    Sub Test B           |   50     1      1       2     54  3.1s │  ← 子テストセット
│      Deep Test          |   25     0      1       0     26  1.5s │  ← 孫テストセット
└──────────────────────────────────────────────────────────────────┘
```

### ヘッダー部

| No | 項目名 | 説明 | データ取得元 | 表示形式 |
|----|-------|------|-------------|---------|
| 1 | Test Summary: | ヘッダーラベル | 固定文字列 | 太字、左寄せ（align幅） |
| 2 | Pass | 成功テスト数 | `tc.passes + tc.cumulative_passes` | 太字、緑色、右寄せ |
| 3 | Fail | 失敗テスト数 | `tc.fails + tc.cumulative_fails` | 太字、赤色、右寄せ |
| 4 | Error | エラーテスト数 | `tc.errors + tc.cumulative_errors` | 太字、赤色、右寄せ |
| 5 | Broken | 既知の失敗テスト数 | `tc.broken + tc.cumulative_broken` | 太字、黄色、右寄せ |
| 6 | Total | 合計テスト数 | pass + fail + error + broken | 太字、青色、右寄せ |
| 7 | Time | 実行時間 | `format_duration(ts)` | 太字、右寄せ |

### 明細部

| No | 項目名 | 説明 | データ取得元 | 表示形式 | 列幅 |
|----|-------|------|-------------|---------|-----|
| 1 | テストセット名 | テストセットの説明 | `ts.description` | 左寄せ、インデント付き | align幅 |
| 2 | Pass | 成功数 | `tc.passes + tc.cumulative_passes` | 右寄せ、緑色 | pass_width |
| 3 | Fail | 失敗数 | `tc.fails + tc.cumulative_fails` | 右寄せ、赤色 | fail_width |
| 4 | Error | エラー数 | `tc.errors + tc.cumulative_errors` | 右寄せ、赤色 | error_width |
| 5 | Broken | 既知の失敗数 | `tc.broken + tc.cumulative_broken` | 右寄せ、黄色 | broken_width |
| 6 | Total | 合計数 | 上記の合計 | 右寄せ、青色 | total_width |
| 7 | Time | 実行時間 | `format_duration(ts)` | 右寄せ | duration_width |

### フッター部

| No | 項目名 | 説明 | データ取得元 | 表示形式 |
|----|-------|------|-------------|---------|
| 1 | RNG情報 | 乱数生成器の状態 | `get_rng(ts)` | テスト失敗時のみ出力 |

## 出力条件

### 抽出条件

| 条件名 | 説明 | 必須 |
|-------|------|-----|
| verbose | 全テストセットを表示（デフォルト: false） | No |
| showtiming | 実行時間を表示（デフォルト: true） | No |
| failfast | 最初の失敗で停止 | No |

### ソート順

テストセットの実行順序で表示（ソートなし）。

### 改ページ条件

テキスト出力のため改ページなし。

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

### 参照テーブル一覧

本帳票はデータベースを参照しない。`DefaultTestSet` の内部データ構造から結果を取得する。

| テーブル名 | 用途 | 結合条件 |
|-----------|------|---------|
| DefaultTestSet.results | テスト結果の格納 | 階層的な親子関係 |

### テーブル別参照項目詳細

#### DefaultTestSet

| 参照項目（カラム名） | 帳票項目との対応 | 取得条件 | 備考 |
|-------------------|----------------|---------|------|
| description | テストセット名 | 直接参照 | String |
| n_passed | Pass（直接） | @atomic :monotonic | Passは結果配列に保存されない |
| results | Fail/Error/Broken | @lock results_lock | 型判定でカウント |
| time_start / time_end | Time | 直接参照 | 差分で計算 |
| verbose | 表示制御 | 直接参照 | trueなら全テストセット表示 |
| rng | RNG情報 | get_rng() | 失敗時のみ出力 |

## 計算仕様

### 計算項目一覧

| 項目名 | 計算式 | 端数処理 | 備考 |
|-------|-------|---------|------|
| passes | `ts.n_passed`（直接） + results内のPass数 | なし | n_passedはatomic |
| fails | results内のFail数 | なし | `isa(t, Fail)` でカウント |
| errors | results内のError数 | なし | `isa(t, Error)` でカウント |
| broken | results内のBroken数 | なし | `isa(t, Broken)` でカウント |
| cumulative_* | 子テストセットの再帰的集計 | なし | `get_test_counts(t)` を再帰呼び出し |
| duration | `time_end - time_start` | 小数1桁（秒） / m:ss形式（60秒以上） | `format_duration()` |
| align | テストセット名の最大表示幅 | なし | `get_alignment()` で再帰的に計算 |
| 各列幅 | `max(headerWidth, ndigits(value))` | なし | 値が0の列は非表示 |

## 処理フロー

### 出力フロー

```mermaid
flowchart TD
    A["@testset 終了"] --> B["finish(ts::DefaultTestSet)"]
    B --> C{テストセット深度 == 0?}
    C -->|No| D["親テストセットに record"]
    C -->|Yes| E["get_test_counts(ts)"]
    E --> F["print_test_results(ts)"]
    F --> G["get_alignment(ts, 0) で列位置計算"]
    G --> H["ヘッダー行出力"]
    H --> I["print_counts() で各行を再帰出力"]
    I --> J{total != pass + broken?}
    J -->|Yes| K["RNG情報出力"]
    J -->|No| L["終了"]
    K --> L
```

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 表示メッセージ | 対処方法 |
|----------|---------|--------------|---------|
| TestSetException | total != pass + broken | テスト結果サマリーの後に例外がスロー | テストの失敗を修正する |
| 二重finish | テストセットが2回以上finishされた | "Test set was finished more than once" | テストセットのライフサイクルを確認 |
| FailFastError | failfast=true で失敗発生 | テスト実行を即座に中止 | 全テスト実行するにはfailfastを無効化 |

## パフォーマンス要件

| 項目 | 内容 |
|-----|------|
| 想定データ件数 | テストセット数に依存（通常は数百程度） |
| 目標出力時間 | ほぼ即座 |
| 同時出力数上限 | 1 |

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

テスト結果レポートにはテストセットの説明文字列とソースファイルのパス情報が含まれる。RNG 状態も出力される場合があるが、セキュリティ上の重大なリスクは低い。

## 備考

- `verbose=true` を設定すると、全テストパスした子テストセットも表示される。デフォルトでは、全テスト成功のサブセットは非表示。
- `TESTSET_PRINT_ENABLE[]` でレポート出力自体を無効化可能。
- `TestCounts` 構造体で結果集計がメモ化される。
- `anynonpass` フィールドで非パス結果の有無を高速に判定可能（atomic操作）。
- `format_duration` は60秒未満なら `"X.Xs"`、60秒以上なら `"Xm XXs"` 形式で表示。

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | Test.jl | `stdlib/Test/src/Test.jl` | `DefaultTestSet` 構造体（行1498-1520）で、テストセットのフィールド（description, verbose, results, n_passed, time_start, time_end, anynonpass, rng）を理解する |
| 1-2 | Test.jl | `stdlib/Test/src/Test.jl` | `TestCounts` 構造体（行1795-1806）で、結果集計用のデータ構造を理解する |

**読解のコツ**: `n_passed` は `@atomic` フィールドで、Pass結果はメモリ節約のためにresults配列に保存されない。`anynonpass` は3値（0x00:未計算, 0x01:全パス, 0x02:非パスあり）のメモ化フィールド。

#### Step 2: 結果集計処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | Test.jl | `stdlib/Test/src/Test.jl` | `get_test_counts(ts::DefaultTestSet)` 関数（行1824-1845）で、再帰的な結果集計ロジックを理解する |
| 2-2 | Test.jl | `stdlib/Test/src/Test.jl` | `format_duration(ts::DefaultTestSet)` 関数（行1881-1889）で、実行時間のフォーマットを理解する |

**主要処理フロー**:
- **行1825**: 直接の結果（passes=n_passed, fails, errors, broken）を初期化
- **行1828-1839**: results配列を走査し、型判定でカウント。子テストセットは再帰的に集計
- **行1843**: anynonpass をメモ化

#### Step 3: 出力処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | Test.jl | `stdlib/Test/src/Test.jl` | `print_test_results(io::IO, ts, depth_pad)` 関数（行1649-1705）でメインの出力ロジックを理解する |
| 3-2 | Test.jl | `stdlib/Test/src/Test.jl` | `get_alignment(ts, depth)` 関数（行1859-1870）で列位置計算を理解する |

**主要処理フロー**:
- **行1652-1670**: 各カテゴリの合計と列幅を計算
- **行1673**: 最大列位置（align）を計算
- **行1675-1695**: ヘッダー行の出力（各カテゴリのヘッダー、色付き、太字）
- **行1697**: `print_counts()` で各テストセット行を再帰的に出力
- **行1699-1704**: 失敗がある場合はRNG情報を出力

#### Step 4: finish処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | Test.jl | `stdlib/Test/src/Test.jl` | `finish(ts::DefaultTestSet)` 関数（行1709-1741）で、テストセット完了時の処理フローを理解する |

**主要処理フロー**:
- **行1710**: `@atomicswap ts.time_end = time()` で二重finish防止
- **行1715-1719**: ネストされたテストセットの場合は親に record
- **行1728-1729**: 最上位テストセットの場合は `print_test_results` を呼び出し
- **行1733-1736**: 失敗があれば `TestSetException` をスロー

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

```
@testset "name" begin ... end
    |
    +-- DefaultTestSet("name", ...)          # テストセット作成
    +-- record(ts, Pass/Fail/Error/Broken)   # 各テスト結果を記録
    +-- finish(ts)                           # テストセット完了
            |
            +-- (深度 > 0) record(parent, ts)
            +-- (深度 == 0) get_test_counts(ts)
            |                   |
            |                   +-- 再帰的に子テストセットを集計
            |
            +-- print_test_results(ts)
                    |
                    +-- get_test_counts(ts)
                    +-- get_alignment(ts, 0)
                    +-- ヘッダー行出力（printstyled）
                    +-- print_counts(io, ts, ...)  # 再帰的に各行出力
                    +-- (失敗時) get_rng(ts) + 出力
```

### データフロー図

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

DefaultTestSet
    +-- n_passed (atomic)   ──> get_test_counts()
    +-- results[]           ──>    |
        +-- Fail                   +-- TestCounts
        +-- Error                  |
        +-- Broken                 +-- print_test_results()
        +-- 子TestSet              |       |
                                   |       +-- ヘッダー行
                                   |       +-- print_counts() (再帰)
                                   |       +-- RNG情報
                                   |
                                   +── stdout (表形式テキスト)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| Test.jl | `stdlib/Test/src/Test.jl` | ソース | DefaultTestSet, TestCounts, print_test_results, finish, get_test_counts, get_alignment, format_duration |
