---
paths: ["TIASshot.Tests/**", "TIASshot/**/*.cs"]
---

# テスト方針 (Testing Policy)

本プロジェクトのテストの種類・粒度・対象範囲・実行タイミングを定める．
具体的な回帰テスト手順は [TEST_01_テスト仕様](../../docs/06_TEST/TEST_01_テスト仕様.md) を参照する（本 rule は方針，TEST_01 は具体手順という役割分担）．

## 基本方針 (Basic Policy)

本プロジェクトは実機カメラ（Lucam / ImagingSource）・光源・ネイティブライブラリ（OpenCvSharp）に強く依存する一方で，画像処理・色補正・設定読み込み等のハードウェアに依存しない純粋ロジックも含む．この性質に合わせ，コードを 2 層に分けてテストする．

- **ピュアロジック層**: ハードウェア・UI・スレッドに依存しない計算/変換ロジック．**自動テスト（単体テスト）で回帰を担保する**．
- **ハードウェア結合層**: カメラ SDK・`Form1`（WinForms UI）・撮影/プレビューのスレッド・コールバックに密結合した処理．**自動テストの対象外**とし，ビルドテスト＋手動テスト＋実機回帰テストで担保する．

「テストできない」と「テストしていない」を混同しない．ハードウェア結合層は原理的に自動テストが困難なため対象外とするが，ピュアロジック層は自動テスト可能であり，必ずテストを書く．

標準に則る．NUnit の慣例（AAA: Arrange-Act-Assert）に従い，テストは実装のなぞり書きではなく**仕様（期待される入出力）を基準**に書く．

## アサーションの書き方 (Assertion Style)

**制約モデル（`Assert.That` ＋ `Is.*` / `Does.*`）のみを使う**．Classic assertion（`Assert.AreEqual`・`Assert.IsTrue`・`StringAssert.Contains` 等）は新規に書かないこと．

理由: NUnit 4 で Classic assertion は `Assert` から削除され `NUnit.Framework.Legacy.ClassicAssert` へ移動した．一方で制約モデルは NUnit 3 でも 4 でも同じく動くため，制約モデルに揃えておけば NUnit のメジャー更新にテストコードの変更なしで追従できる（既存の 209 箇所は 2026-08-28 に移行済み）．

| 用途 | 書き方 |
| --- | --- |
| 等値 | `Assert.That(actual, Is.EqualTo(expected))` |
| 浮動小数点の等値 | `Assert.That(actual, Is.EqualTo(expected).Within(delta))` |
| 真偽 | `Assert.That(x, Is.True)` / `Assert.That(x, Is.False)` |
| null | `Assert.That(x, Is.Null)` / `Assert.That(x, Is.Not.Null)` |
| 同一性 | `Assert.That(actual, Is.SameAs(expected))` |
| 部分文字列 | `Assert.That(actual, Does.Contain(expected))` |
| 例外 | `Assert.Throws<T>((Action)(() => ...))` / `Assert.DoesNotThrow((Action)(() => ...))` |

- **例外系のラムダは `(Action)` にキャストする**．NUnit 4.6 は `TestDelegate` 版（`[Obsolete]`）と `Action` 版（`[OverloadResolutionPriority]`）の両方を持ち，LangVersion 7.3 のテストプロジェクトでは優先度属性が解釈されず CS0121（呼び出しが曖昧）になる．LangVersion は古い VS / Build Tools との互換のため上げない（2026-09-29 の NUnit 4.6.1 移行で 16 箇所を対応）．

- **引数の順序に注意する**．Classic は `(expected, actual)` だが制約モデルは `(actual, 制約)` である．とくに `Does.Contain` は第 1 引数が**検査対象**で，期待する部分文字列は制約の側に入る（逆にすると意味が反転したまま通ってしまう場合がある）．
- 浮動小数点の比較で `.Within(...)` を落とさないこと．厳密比較になりフレーキーになる．

## テストの種類 (Test Types)

| 種類 | 対象 | 自動化 | 実行タイミング |
| --- | --- | --- | --- |
| 単体テスト | ピュアロジック層 | 自動（NUnit） | コミット前・`/implement` Phase 2 |
| ビルドテスト | ソリューション全体 | 自動（MSBuild） | 全コミット前 |
| 回帰テスト | 色補正・チャート検出の出力 | 半自動（基準データ比較） | リファクタ・修正の前後 |
| セルフテスト | 依存ロード・偽カメラでのプレビュー経路・合成チャートでの校正〜保存 | 自動（`--selftest`．`/verify`・`/implement` Phase 1b） | 依存更新・ハードウェア結合層の変更後，手動テストの前段 |
| 手動テスト | 起動〜撮影〜保存の一連動作 | 手動（実機） | 機能追加・修正の完了時 |

回帰テスト・手動テストの具体手順は [TEST_01_テスト仕様](../../docs/06_TEST/TEST_01_テスト仕様.md) に記載する．

## テスト対象の切り分け (Test Scope)

どのクラス・処理を自動テストの対象とするかを明示する．新しいコードを追加した際は，この表に従って対象/対象外を判断する．

### 自動テスト対象（ピュアロジック層）

| 対象 | テスト内容の例 |
| --- | --- |
| `ColorCorrection/ColorCorrector` | 次元拡張（`ExtendMat`）・変換行列算出・色変換の入出力．既知の入力 Mat に対する出力 Mat／数値の検証 |
| `ChartDetection/ChartDetector` | 既知のチャート画像を入力したときのマスク位置・検出結果の幾何的検証 |
| `Config` | 設定 XML のパース・型変換・範囲フィルタ（例: チャネル数の `4〜17` 絞り込み） |
| `Utils/IoUtil` | CSV の読み書き往復（`SaveMatToCsv` → `LoadMatFromCsv`）で値が保たれること |
| ゲイン更新比率の計算（`GetRatio` 相当） | 入力値・目標値・更新レートに対する比率計算 |

### 自動テスト対象外（ハードウェア結合層）

| 対象 | 対象外の理由 |
| --- | --- |
| `Cameras/CameraBase`・`IScam`・`Lucam` のカメラ制御・撮影/プレビュー | カメラ SDK（`dll.*` / `TIS.Imaging.*`）・フレームコールバック・実機に依存 |
| 撮影/保存のスレッド連携（`RunShotLoop` / `SaveThread`） | カメラフレーム取得・`Form1` に依存．スレッド安全性は設計レビューと実機で担保 |
| `UI/Form1`・`UI/PreviewMonitor` | WinForms UI スレッド・画面描画に依存 |
| `LightSource` | 光源のシリアル通信（実機）に依存 |

※ ハードウェア結合層に手を入れた場合は，自動テストの代わりに**設計レビュー＋実機回帰**で挙動を確認する．その前段として，実機なしで通せる範囲はセルフテスト（`python scripts/verify/run_selftest.py`．`.claude/verify-profile.md`）で確認できる．セルフテスト部品のうちピュアロジック（`SelfTestReport`・`SelfTestChart`・引数判定）は NUnit の対象（`#if SELFTEST`）で，偽カメラ・ヘッドレス Form1 の流れはセルフテストの実行自体で担保する．`/implement` の Tester フェーズでは，対象がハードウェア結合層の場合，無理にモックを作らず設計レビューで判断してよい．

## フレームワークと配置 (Framework & Layout)

- **フレームワーク**: NUnit（`NUnit3TestAdapter` で Visual Studio / `dotnet test` から実行）．
- **テストプロジェクト**: `TIASshot.Tests`（**x64 / .NET Framework 4.8**．本体と同一ターゲット）．
- **ネイティブ依存**: `ColorCorrector` 等のテストは OpenCvSharp のネイティブ DLL をロードするため，テストプロジェクトも **x64 必須**かつ OpenCvSharp ランタイムを参照すること（プラットフォームを `AnyCPU` にすると Mat 演算が実行時に失敗する）．
- **配置**: ソリューションルート直下にテストプロジェクトを置く．

```text
TIASshot/                  ← ソリューションルート
├── TIASshot.sln
├── TIASshot/             ← 本体プロジェクト
└── TIASshot.Tests/       ← テストプロジェクト（NUnit）
    ├── ColorCorrectorTests.cs
    ├── ConfigTests.cs
    └── ...
```

- **命名**: テストクラスは `<対象クラス>Tests`，テストメソッドは「対象_条件_期待結果」が分かる名前にする（日本語名も可）．
- **粒度**: 1 テストにつき 1 つの検証観点とする．1 メソッドで多数の `Assert` を詰め込まない．
- **テストデータ**: 入力画像・基準 CSV 等は `TIASshot.Tests` 配下の専用フォルダ（例: `TestData/`）に置き，テストから相対参照する．

## テストの実行 (Running Tests)

テストプロジェクトは本体に合わせたレガシー形式（packages.config）のため `dotnet test` は使えない．ビルド後に `vstest.console.exe` を **x64** で実行する（ネイティブ DLL を x64 でロードするため）．

**具体的なコマンドは [ENV_04_開発コマンド](../../docs/02_ENV/ENV_04_開発コマンド.md) を参照する**（本 rule は方針，ENV_04 はコマンドという役割分担）．MSBuild・`vstest.console.exe` の場所は VS のエディション（Community / Professional / Enterprise / Build Tools）によって変わるため，パスを直書きせず ENV_04 の `vswhere` による解決手順を使うこと．

Visual Studio 上ではテストエクスプローラーから実行できる（プラットフォームを x64 にすること）．

## モック方針 (Mocking)

- **過剰なモックを避ける**．ピュアロジックは実値（実際の Mat・数値）で検証する．
- **静的依存の扱い**: `Config` のような静的クラスへの依存がテストの妨げになる場合，テスト容易化のための最小限のシーム（引数化・依存の注入）を導入してよい．ただし**本番の挙動は変えない**こと．
- **ハードウェア SDK はモックしない**．モックしてまでテストする価値が低く，対象外（ハードウェア結合層）とする方針と整合させる．

## カバレッジ方針 (Coverage)

- **数値的なカバレッジノルマは課さない**．カバレッジ率の達成自体を目的化しない．
- 代わりに，**重要な計算経路を網羅する**ことを定性的な目標とする．具体的には，色変換行列の算出・次元拡張・CSV 往復・ゲイン更新比率といった「誤ると出力が壊れる中核ロジック」にテストを行き渡らせる．

## 既存ドキュメントとの関係 (Relation to Existing Docs)

- 本 rule（`.claude/rules/testing.md`）は**テストの方針**を定める．
- [TEST_01_テスト仕様](../../docs/06_TEST/TEST_01_テスト仕様.md) は**回帰テスト・手動テストの具体手順**（基準データの作成・比較方法・合格条件）を定める．
- 自動テスト（単体テスト）の具体的なケースが増えた場合は，`docs/06_TEST/` に追記する（本 rule には方針のみ残し，個別ケースは重複させない）．
