# テスト仕様 (Test Specification)

## 方針 (Policy)

リファクタリング等，挙動を変えない変更で処理が変わっていないことを確認するためのテスト仕様．

テストの種類:

- ビルドテスト: コンパイルエラーがないことを確認する
- 結合ロジックテスト: 偽カメラとヘッドレス Form1 で，撮影・保存の流れと失敗時の通知を NUnit で自動確認する（`[Category("Integration")]`．方針は `.claude/rules/testing.md`，移行計画は [PLAN_05](../03_PLAN/PLAN_05_テスト網羅性向上計画.md)）
- 回帰テスト: リファクタリング前後の出力が一致することを確認する
- セルフテスト: 実機なしで依存ロード・プレビュー経路・校正〜保存を自動確認する（手動テストの前段）
- 実機テスト: Claude が実機のカメラと光源を操作して，接続・プレビュー・露光/ゲイン・光源・閉じる処理・再接続を自動確認する（ユーザーの合図があるときだけ．手動テストの前段）
- 手動テスト: 実機を使って動作を目視確認する

## セルフテスト (Self Test)

依存更新やハードウェア結合層の変更後，手動テストの前に実施する（`Debug|x64` のみ．手順は [ENV_04](../02_ENV/ENV_04_開発コマンド.md) の「セルフテスト」，判定は `.claude/verify-profile.md`．`/verify` から自動実行できる）．

- `python scripts/verify/run_selftest.py`（依存を変えた場合は `--full`）
- **合格条件**: 終了コード 0（`deps`・`preview`・`calib-save` の 3 項目が PASS）
- 代替していない範囲（実カメラ SDK との結合・実プレビュー表示・実チャートの検出性能・画像の見た目・長時間のメモリ/GDI）は，下記の実機テストと手動テストで確認する

## 実機テスト (Hardware Test)

Claude が実機のカメラと光源を操作して，層④のうち数値で判定できる項目を自動で確認する（[PLAN_06](../03_PLAN/PLAN_06_実機自動テスト計画.md)．`Debug|x64` のみ．手順は [ENV_04](../02_ENV/ENV_04_開発コマンド.md) の「実機テスト」，判定としきい値の詳細は `.claude/verify-profile.md` の「実機テスト」節）．**ユーザーの合図があるときだけ実行する**（撮影の本番中に機材を操作しないため）．

- `python scripts/verify/run_hwtest.py --camera <lucam|iscam>`（対象は ImagingSource の `--camera iscam` だけ．Lumenera の実機テストは行わない．PLAN_06「対象機種」）
- **実行の前提**: カメラ・光源のドライバが入っている（[ENV_02](../02_ENV/ENV_02_環境構築手順.md)），検査する機種のカメラと光源が接続されている，カメラの前の穴にカラーチャート（または白板）がはめてある，TIASshot が起動していない
- **合格条件**: 終了コード 0（下表の 7 項目が PASS．結果は `.verify/hwtest/run-<日時>/hwtest-result.txt`）

| 項目 | 確認内容 | 合格条件 |
| --- | --- | --- |
| `connect` | 指定した機種に接続でき，機種名・シリアル番号が取れる | `Connect()` が成功する |
| `preview` | プレビューが流れる | 10 fps 以上で，本モニタに表示用 Bitmap がある |
| `exposure-gain` | 露光・ゲインの設定がフレームに効く | 基準（平均輝度 30〜220・飽和画素 1% 以下）から露光 1 段下げで比 0.8 以下・上げで比 1.25 以上（飽和した向きは確認しない），ゲインは差 3 以上（片側でよい）．元の値に戻る |
| `light` | 光源が点灯・消灯し，閉じた後に開き直せる | 点灯と消灯の平均輝度の差が 100 以上．ポートを開き直した後も点灯が効く |
| `close-preview` | プレビュー中に閉じる処理を呼んでもすぐ戻る | 1000 ms 以内に戻り，例外がなく，閉じた後に届くフレームが 2 以下 |
| `reconnect` | 切断した後に再接続できる | 2 回目の `Connect()` が成功し，5 フレーム以上届く |
| `cleanup` | 後始末と書き込み禁止 | 露光・ゲインを戻す → カメラ切断 → 光源のポートを閉じる（消灯しない）が成功し，設定・デバイス状態ファイル・本番の保存先が変わっていない |

- 2026-10-08 に ImagingSource（DFK 33UX178 の 2 個体）で 7/7 合格した．Lumenera の実機テストは行わない（解像度が相対的に低く今後あまり使わない見込みのため）
- チャートが要る項目（校正・撮影・保存中や校正中に閉じる．PLAN_06 フェーズ 2）は未実装のため，下記の手動テストで確認する

## 結合ロジックテスト (Integration Logic Tests)

NUnit の全件実行に含まれる（ENV_04 の「テスト」．カテゴリで絞る場合は `TestCategory=Integration`）．現在のケース:

| テスト | 確認内容 |
| --- | --- |
| `CameraBaseSaveIntegrationTests`（単発・保存成功） | 撮影画像・sRGB 変換画像・Info.csv が保存され，「全ての画像保存完了」が表示され，撮影ボタンが再び有効になる |
| `CameraBaseSaveIntegrationTests`（単発・撮影画像の保存失敗） | 保存失敗が通知され，「全ての画像保存完了」が表示されず，撮影ボタンが再び有効になる |
| `CameraBaseSaveIntegrationTests`（連続 3 枚・2 枚目の保存失敗） | 同上 |
| `CameraBaseCalibrationIntegrationTests`（校正） | 校正完了で撮影ボタンが無効から有効になり，校正保存ファイル（SPEC_01）が作られる．チャートが映らないと完了せずボタンは無効のまま．途中で例外が起きても再検出して完了し，旧チャートマスクは解放される |
| `CameraBaseCalibrationIntegrationTests`（切断） | 校正後・校正中の切断で例外にならず，チャートマスクが解放される |
| `CameraBaseShotIntegrationTests`（撮影ループ） | 連続撮影で枚数分のファイルと撮影ログ（間隔どおりの時刻）．枚数 0 で例外にならずボタンが戻る．フレーム取得の失敗で中止・通知され，Info.csv の中止行は理由にカンマ・改行・引用符を含んでも 3 列に収まる |
| `CameraBaseShotIntegrationTests`（保存キュー・切断） | 保存失敗時に撮影した全フレームが解放される．前回のキューに残った画像が解放される．保存中の切断で例外にならず保存が完了する |
| `Form1UiIntegrationTests` | データ名・連続撮影の枚数・間隔をワーカースレッドから読んでも UI の入力どおりの値が返る（数値でない入力を含む）．ShowMessage・EnableShots が UI に反映され，ワーカーからの呼び出しは UI スレッドを待たずに戻る．閉じ始めた後・ハンドル未生成では何もしない |
| `CameraBaseCalibrationIntegrationTests`（校正の失敗） | 校正中の例外はカメラのコールバックへ伝わらず，失敗の通知はちょうど 1 回．失敗後は検出中の表示に上書きされず失敗の表示が続き，再校正が完了すると「自動校正完了」になる．校正画像の保存に失敗しても校正済みのまま警告を表示する |
| `CameraBaseShotIntegrationTests`（撮影中の保存） | 連続撮影の途中でも保存が進む（4 枚目の取得時点で 2 枚目が保存済み） |
| `Form1CloseIntegrationTests` | 保存中に閉じると取り消されて「保存中」と表示される．保存後は閉じ始め → タイマー → カメラ → 光源の順に後始末される．プレビュー中に閉じてもすぐ戻り例外にならない |

- **合格条件**: 全件緑．新しいケースを追加したときは，取り込む前に 3 回連続で緑になることを確認する

## ビルドテスト (Build Test)

全フェーズで実施する．

**手順**

1. Visual Studio で「ビルド」→「ソリューションのビルド」を実行する
2. エラーが0件であることを確認する
3. 警告が増加していないことを確認する（リファクタリング前の警告数を記録しておく）

**合格条件**

- ビルドエラーが0件

## 回帰テスト: 色補正処理（フェーズ2）

リファクタリング前に基準データを作成し，リファクタリング後の出力と比較する．

### 基準データの作成（リファクタリング前に実施）

1. 実機でキャリブレーションを実行する
2. 保存された以下のファイルを「基準データ」として別フォルダに保管する
   - `TCC_RGB.csv` ← チャート24色票のRGB値
   - `Conv_RGB-sRGB{CN}.csv` ← RGB→sRGB 変換行列
   - `Conv_RGB-XYZ{CN}.csv` ← RGB→XYZ 変換行列
   - `Conv_sRGB-XYZ{CN}.csv` ← sRGB→XYZ 変換行列
   - `TCC_sRGB{CN}.csv` ← チャートのsRGB変換値
   - `sRGB{CN}_0001.jpg` ← 撮影した sRGB 変換画像1枚

### 比較（リファクタリング後に実施）

1. 同一のキャリブレーション画像・撮影条件で再度実行する
   - ※ 実機でのキャリブレーション再現が困難な場合は，TCC_RGB.csv を直接入力として行列計算のみ比較する
2. 基準データと出力ファイルを比較する

**合格条件**

- CSV の数値が全行・全列で一致する（または浮動小数点誤差 ±1e-6 以内）
- 画像のピクセル値が全ピクセルで一致する

## チャート検出テスト（フェーズ3）

保存済みの実画像を入力として，チャートマスクの位置を比較する．

※ 2026-08-29 に自動テスト `ChartDetectorTests` を追加した（合成した AR マーカー画像を入力し，早期 return・上下逆判定・固定判定・24 枚のマスク生成を検証する）．**本節の実機による目視比較を置き換えるものではない**．自動テストが担保するのは検出ロジックの分岐と幾何であり，実写のノイズ・照明ムラ・レンズ歪み・実際のマーカー印刷品質に対する検出性能は実機でしか確認できない．OpenCvSharp のネイティブ側更新（例: 4.10 → 4.13）を行ったときは，自動テストが緑でも本節の実機比較を必ず実施すること．

### 基準データの作成（リファクタリング前に実施）

1. キャリブレーション時に保存される `TCC_ROIs.jpg` を基準データとして保管する
   - ※ TCC_ROIs.jpg はチャートの ROI マスク位置を緑色で重ねた確認画像

### 比較（リファクタリング後に実施）

1. 同一条件でキャリブレーションを実行する
2. 出力された `TCC_ROIs.jpg` を基準データと目視比較する
   - ※ 緑色のマスク位置が完全に一致していることを確認する

**合格条件**

- マスクの位置・形状が基準データと目視で一致する

## 手動テスト（全フェーズ）

実機を使った動作確認．フェーズごとに実施する．

**確認項目**

- アプリが正常に起動する
- カメラが自動で接続される（Lucam または ImagingSource）
  - ※ 指定した機種への接続そのものは実機テスト（`connect`）でも確認できる．自動で機種を選んで接続する流れは手動で確認する
- プレビューが表示される
  - ※ フレームが流れることは実機テスト（`preview`）でも確認できる．見た目は手動で確認する
- 舌診チャートを映すとキャリブレーションが自動開始される
- キャリブレーション完了後に撮影ボタンが有効になる
- 1枚撮影が正常に完了し，ファイルが保存される
- 連続撮影が正常に完了し，ファイルが保存される
- 光源の ON/OFF が切り替わる
  - ※ 実機テスト（`light`）でも確認できる
- アプリを閉じてもエラーが出ない（閉じた後も光源は点いたまま残るのが仕様）
  - ※ プレビュー中に閉じる場合は実機テスト（`close-preview`）でも確認できる

**合格条件**

- 上記の全項目でエラー・クラッシュが発生しない
