diff --git a/.claude/rules/testing.md b/.claude/rules/testing.md index 6d759f6..4c87451 100644 --- a/.claude/rules/testing.md +++ b/.claude/rules/testing.md @@ -9,12 +9,20 @@ ## 基本方針 (Basic Policy) -本プロジェクトは実機カメラ(Lucam / ImagingSource)・光源・ネイティブライブラリ(OpenCvSharp)に強く依存する一方で,画像処理・色補正・設定読み込み等のハードウェアに依存しない純粋ロジックも含む.この性質に合わせ,コードを 2 層に分けてテストする. +本プロジェクトは実機カメラ(Lucam / ImagingSource)・光源・ネイティブライブラリ(OpenCvSharp)に強く依存する一方で,画像処理・色補正・設定読み込み等のハードウェアに依存しない純粋ロジックも含む. -- **ピュアロジック層**: ハードウェア・UI・スレッドに依存しない計算/変換ロジック.**自動テスト(単体テスト)で回帰を担保する**. -- **ハードウェア結合層**: カメラ SDK・`Form1`(WinForms UI)・撮影/プレビューのスレッド・コールバックに密結合した処理.**自動テストの対象外**とし,ビルドテスト+手動テスト+実機回帰テストで担保する. +**目標は,人間による実機確認を「SDK の実呼び出し」と「実画像の見た目」だけに絞ること**である.そのため,実機でしか確かめられないものを最小限に切り分け,それ以外はすべて自動テストで担保する.コードを次の 4 層に分けてテストする. -「テストできない」と「テストしていない」を混同しない.ハードウェア結合層は原理的に自動テストが困難なため対象外とするが,ピュアロジック層は自動テスト可能であり,必ずテストを書く. +| 層 | 対象 | テスト手段 | +| --- | --- | --- | +| ① ピュアロジック層 | ハードウェア・UI・スレッドに依存しない計算/変換ロジック | **NUnit で必ず自動テスト** | +| ② 結合ロジック層 | `CameraBase` の校正・撮影・保存の流れ,`Form1` の UI 連携メソッド | **NUnit+偽カメラ+ヘッドレス `Form1` で自動テスト**(後述「結合ロジック層のテスト」) | +| ③ SDK アダプタ層 | `IScam`・`Lucam`・`LightSource` | 判断ロジックを純粋関数に切り出して層①で検証する.SDK を呼ぶ部分そのものは層④(後述「SDK アダプタの書き方」) | +| ④ 実機層 | SDK の実呼び出し・実画像の見た目 | 手動テスト(TEST_01).ここに残す項目は最小限にする | + +「テストできない」と「テストしていない」を混同しない.実機が本当に要るのは SDK の実呼び出しと見た目だけであり,それ以外(状態遷移・スレッド連携・失敗時の通知等)は層②・③の手段でテストできる.「UI だから」「スレッドだから」を理由にテストを省かない. + +※ 本方針(4 層モデル)は 2026-10-06 に旧方針(ピュアロジック層/ハードウェア結合層の 2 層)から改訂した.層②・③の基盤は [PLAN_05](../../docs/03_PLAN/PLAN_05_テスト網羅性向上計画.md) のフェーズに沿って整備中である.基盤が整うまでの間,層②・③に当たる変更は「後述の手段で書けるなら書く.書けないなら理由を明示して設計レビュー+セルフテスト+実機で担保する」とする. 標準に則る.NUnit の慣例(AAA: Arrange-Act-Assert)に従い,テストは実装のなぞり書きではなく**仕様(期待される入出力)を基準**に書く. @@ -43,11 +51,14 @@ | 種類 | 対象 | 自動化 | 実行タイミング | | --- | --- | --- | --- | -| 単体テスト | ピュアロジック層 | 自動(NUnit) | コミット前・`/implement` Phase 2 | +| 単体テスト | 層①(層③から切り出した判断ロジックを含む) | 自動(NUnit) | コミット前・`/implement` Phase 2 | +| 結合ロジックテスト | 層② | 自動(NUnit.`[Category("Integration")]`) | コミット前・`/implement` Phase 2 | | ビルドテスト | ソリューション全体 | 自動(MSBuild) | 全コミット前 | -| 回帰テスト | 色補正・チャート検出の出力 | 半自動(基準データ比較) | リファクタ・修正の前後 | -| セルフテスト | 依存ロード・偽カメラでのプレビュー経路・合成チャートでの校正〜保存 | 自動(`--selftest`.`/verify`・`/implement` Phase 1b) | 依存更新・ハードウェア結合層の変更後,手動テストの前段 | -| 手動テスト | 起動〜撮影〜保存の一連動作 | 手動(実機) | 機能追加・修正の完了時 | +| 回帰テスト | 色補正・チャート検出の出力(合成チャート,および実機で録画したチャート・白板のフレーム) | 自動(基準データ比較) | リファクタ・修正の前後 | +| セルフテスト | 依存ロード・偽カメラでのプレビュー経路・合成チャートでの校正〜保存 | 自動(`--selftest`.`/verify`・`/implement` Phase 1b) | 依存更新・層②〜④の変更後,手動テストの前段 | +| 手動テスト | SDK の実呼び出し・実画像の見た目(層④) | 手動(実機) | 機能追加・修正の完了時 | + +※ 実機録画データ(舌診チャート・白板のフレーム)による回帰は PLAN_05 のフェーズ 4 で整備する.**舌の画像は個人情報のためテストデータに含めない**. 回帰テスト・手動テストの具体手順は [TEST_01_テスト仕様](../../docs/06_TEST/TEST_01_テスト仕様.md) に記載する. @@ -55,26 +66,59 @@ どのクラス・処理を自動テストの対象とするかを明示する.新しいコードを追加した際は,この表に従って対象/対象外を判断する. -### 自動テスト対象(ピュアロジック層) +### 層①: ピュアロジック層 | 対象 | テスト内容の例 | | --- | --- | | `ColorCorrection/ColorCorrector` | 次元拡張(`ExtendMat`)・変換行列算出・色変換の入出力.既知の入力 Mat に対する出力 Mat/数値の検証 | | `ChartDetection/ChartDetector` | 既知のチャート画像を入力したときのマスク位置・検出結果の幾何的検証 | | `Config` | 設定 XML のパース・型変換・範囲フィルタ(例: チャネル数の `4〜17` 絞り込み) | -| `Utils/IoUtil` | CSV の読み書き往復(`SaveMatToCsv` → `LoadMatFromCsv`)で値が保たれること | -| ゲイン更新比率の計算(`GetRatio` 相当) | 入力値・目標値・更新レートに対する比率計算 | +| `Utils/IoUtil` | CSV の読み書き往復(`SaveMatToCsv` → `LoadMatFromCsv`)で値が保たれること.保存先フォルダ・ファイル名の組み立て | +| `Utils/ImageStats`・`Utils/BitmapUtil` | 画像統計・JSON 出力,Bitmap の複製(所有権を含む) | +| ゲイン更新比率の計算(`GetRatio`・`GainCalc` 相当) | 入力値・目標値・更新レートに対する比率計算.非有限値・範囲端の扱い | +| 層③から切り出した判断ロジック | ROI とフレームサイズの照合,ゲインの算出,バッファサイズの検査,例外を通知に変える処理 等 | -### 自動テスト対象外(ハードウェア結合層) +### 層②: 結合ロジック層 + +| 対象 | テスト内容の例 | +| --- | --- | +| `CameraBase` の校正 | チャート検出 → 固定判定 → 変換行列算出 → 撮影ボタン有効化の状態遷移.途中で失敗したときに再検出できる状態へ戻ること | +| `CameraBase` の撮影・保存(`RunShotLoop`・`SaveThread`・`SaveImages`) | 単発・連続撮影で期待どおりのファイルが保存されること.保存失敗時に成功と表示しないこと.残りフレームの破棄・解放 | +| `CameraBase` の切断・終了 | 保存中・校正中に切断したときの後始末の順序 | +| `UI/Form1` の UI 連携メソッド | `ShowMessage`・`ShowError`・`EnableShots`・`GetDataName` 等の結果.UI スレッド外から呼んだときのマーシャリング | + +### 自動テスト対象外(層④: 実機層) | 対象 | 対象外の理由 | | --- | --- | -| `Cameras/CameraBase`・`IScam`・`Lucam` のカメラ制御・撮影/プレビュー | カメラ SDK(`dll.*` / `TIS.Imaging.*`)・フレームコールバック・実機に依存 | -| 撮影/保存のスレッド連携(`RunShotLoop` / `SaveThread`) | カメラフレーム取得・`Form1` に依存.スレッド安全性は設計レビューと実機で担保 | -| `UI/Form1`・`UI/PreviewMonitor` | WinForms UI スレッド・画面描画に依存 | -| `LightSource` | 光源のシリアル通信(実機)に依存 | +| `IScam`・`Lucam` の SDK 呼び出し(接続・露光/ゲイン設定・フレーム取得・ストリーム停止) | カメラ SDK(`dll.*` / `TIS.Imaging.*`)と実機に依存する.判断ロジックは層③として切り出して層①で検証する | +| `LightSource` のシリアル通信 | 光源(実機)に依存する.ポートを開けなかったときの扱い等の判断は層③として切り出す | +| 画面描画の見た目(`Form1`・`PreviewMonitor` の表示内容) | 目視でしか判断できない.表示用 Bitmap の所有権・更新の流れはセルフテスト(`preview`)で担保する | -※ ハードウェア結合層に手を入れた場合は,自動テストの代わりに**設計レビュー+実機回帰**で挙動を確認する.その前段として,実機なしで通せる範囲はセルフテスト(`python scripts/verify/run_selftest.py`.`.claude/verify-profile.md`)で確認できる.セルフテスト部品のうちピュアロジック(`SelfTestReport`・`SelfTestChart`・引数判定)は NUnit の対象(`#if SELFTEST`)で,偽カメラ・ヘッドレス Form1 の流れはセルフテストの実行自体で担保する.`/implement` の Tester フェーズでは,対象がハードウェア結合層の場合,無理にモックを作らず設計レビューで判断してよい. +※ 層④に手を入れた場合は**設計レビュー+実機回帰**で挙動を確認する.その前段として,実機なしで通せる範囲はセルフテスト(`python scripts/verify/run_selftest.py`.`.claude/verify-profile.md`)で確認できる.`/implement` の Tester フェーズでは,対象が層④だけの場合に限り,テストを書かずに設計レビューで判断してよい.層②・③に当たる部分が含まれるなら,その部分のテストは書く. + +## 結合ロジック層のテスト (Integration Logic Tests) + +層②のテストは,カメラの代わりに偽カメラ(`SelfTest/SelfTestCamera`.`CameraBase` の派生)を,フォームの代わりにヘッドレス `Form1`(`Form1.SelfTest.cs`)を使う.どちらも `SELFTEST` フラグ(Debug 系の構成のみ)でコンパイルされるため,層②のテストも `#if SELFTEST` で囲む. + +- **STA スレッドで動かす**: WinForms のコントロールと `Control.Invoke` は STA スレッドとメッセージ処理を必要とする.テストは共通ハーネス(PLAN_05 フェーズ 1 で作成)を通して,STA スレッド上でヘッドレス `Form1` を生成し,`SelfTestStart` でハンドルを作ってから使う.ハーネスを経由せずに個別のテストでスレッドやメッセージ処理を組まない +- **待機には必ずタイムアウトを付ける**: 「条件が満たされるまでメッセージを処理して待つ」処理は無限待ちにしない.タイムアウトしたらテストを失敗させ,ハングで後続のテストを止めない.層②の各テストには **1 件あたり 15 秒**のタイムアウトを付ける +- **実行時間の目安**: 層②のテスト全体で **30 秒以内**を目安にする.テスト用の一時設定では校正フレーム数(`Calib/Frames`)等を小さくし,1 件あたり 1 秒前後で終わるようにする.目安を超えそうなときは,テストを減らしたり `/verify` から外したりせず,テスト用設定を見直して速くする(`/auto-audit` は修正 1 件ごとにテスト一式を何度も回すため,ここが延びると全作業が遅くなる) +- **観測はアクセサ経由で行う**: 結果は `Form1.SelfTest.cs` のアクセサ(`SelfTestMessage`・`SelfTestLastError`・`SelfTestShotsEnabled` 等)と,保存されたファイルで確かめる.`Form1` の private フィールドをリフレクションで読まない.足りないアクセサは `Form1.SelfTest.cs` に追加する +- **失敗の注入を必ず含める**: 各観点で正常系に加え,失敗を注入するケースを 1 つ以上書く(存在しない保存先,壊れた設定ファイル,チャートが検出できない画像,途中で例外を投げる偽カメラ 等).不具合は異常系に集中しやすい +- **一時ファイルの扱い**: 設定は一時フォルダに複製した `config.xml` を `Config.Load(path)` で読み込み,保存先も一時フォルダにする.同梱の `config.xml` や本番の保存先(`C:\TIAS_Data` 等)には書き込まない.テストの終わりに一時フォルダを削除する +- **カテゴリ**: 層②のテストには `[Category("Integration")]` を付ける(所要時間の把握のため).メモリ量で判定するテスト(リーク再現・ベンチ)には `[Category("Memory")]` を付ける(環境による不安定さが出たときに通常の実行から外せるようにするため) +- **常に実行する**: 層②のテストは `/verify`(`run_selftest.py`)・`/auto-audit`・`/implement` のテスト実行に常に含める.CI が無く,これらが実質唯一の自動チェックのため,カテゴリで分けて普段は回さない運用にはしない +- **安定性の確認**: 層②のテストを新しく追加したときは,取り込む前に 3 回連続で実行して緑になることを確認する + +## SDK アダプタの書き方 (Writing SDK Adapters) + +層③(`IScam`・`Lucam`・`LightSource`)に手を入れるときは,次の形に寄せる(Humble Object パターン). + +- SDK を呼ぶメソッドは「SDK から受け取った値を判断ロジックに渡し,その結果を SDK に渡す」だけの薄い形にする +- 判断(値の検証・計算・分岐・例外を通知に変える処理)は SDK 型に依存しない純粋関数として切り出し,層①でテストする +- 切り出しの対象例: ROI とフレームサイズの照合,ホワイトバランスのゲイン算出(`GainCalc` のように),フレームバッファのサイズ検査,ポートを開けなかったときの扱い +- 既存コードを切り出したときは,切り出した直後に実機確認を 1 回行う(切り出し自体で SDK 呼び出しの順序や値を変えていないことの確認) ## フレームワークと配置 (Framework & Layout) @@ -96,6 +140,8 @@ - **命名**: テストクラスは `<対象クラス>Tests`,テストメソッドは「対象_条件_期待結果」が分かる名前にする(日本語名も可). - **粒度**: 1 テストにつき 1 つの検証観点とする.1 メソッドで多数の `Assert` を詰め込まない. - **テストデータ**: 入力画像・基準 CSV 等は `TIASshot.Tests` 配下の専用フォルダ(例: `TestData/`)に置き,テストから相対参照する. + - 実機録画フレーム(舌診チャート・白板)は **Git LFS** で管理する.可逆形式(PNG)で保存し,1 機種・1 場面あたり 3〜5 フレームに絞る.校正にはこれを繰り返し流して使う + - **舌の画像はテストデータに含めない**(個人情報のため) ## テストの実行 (Running Tests) @@ -109,12 +155,14 @@ - **過剰なモックを避ける**.ピュアロジックは実値(実際の Mat・数値)で検証する. - **静的依存の扱い**: `Config` のような静的クラスへの依存がテストの妨げになる場合,テスト容易化のための最小限のシーム(引数化・依存の注入)を導入してよい.ただし**本番の挙動は変えない**こと. -- **ハードウェア SDK はモックしない**.モックしてまでテストする価値が低く,対象外(ハードウェア結合層)とする方針と整合させる. +- **ハードウェア SDK はモックしない**.SDK の型や関数を偽物に差し替えてテストするのではなく,判断ロジックを SDK から切り出してテストする(層③). +- **偽カメラはモックではなく代替実装として使う**.`SelfTestCamera` は `CameraBase` の派生として実際の校正・撮影・保存の流れを通すもので,SDK を模倣するものではない.失敗を注入したい場合は,偽カメラ(またはテスト用の派生クラス)に失敗させる口を足してよい.ただし本番のコード(`IScam`・`Lucam`)の挙動は変えない. ## カバレッジ方針 (Coverage) - **数値的なカバレッジノルマは課さない**.カバレッジ率の達成自体を目的化しない. -- 代わりに,**重要な計算経路を網羅する**ことを定性的な目標とする.具体的には,色変換行列の算出・次元拡張・CSV 往復・ゲイン更新比率といった「誤ると出力が壊れる中核ロジック」にテストを行き渡らせる. +- 代わりに,**重要な経路を網羅する**ことを定性的な目標とする.具体的には,色変換行列の算出・次元拡張・CSV 往復・ゲイン更新比率といった「誤ると出力が壊れる中核ロジック」と,校正・撮影・保存の流れ(正常系と失敗時)にテストを行き渡らせる. +- **カバレッジは抜けを見つける地図として使う**.テストプロジェクトに導入済みの `Microsoft.CodeCoverage` で「一度も通っていない箇所」を確認し,そこが重要な経路なら優先してテストを足す. ## 既存ドキュメントとの関係 (Relation to Existing Docs) diff --git a/CLAUDE.md b/CLAUDE.md index bb332a1..fd8f482 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,14 +4,14 @@ ## 開発進捗 -最新: OpenCvSharp 4.13 の ArUco API 移行(#31・PR #36)を main に追従させて実機確認のうえマージした +最新: テスト方針を 4 層モデルに改訂し,実機確認を減らす移行計画(PLAN_05)を策定した ※ 本欄は**最新ステップ 1 行のみ上書き更新**.詳細な進捗履歴(動機・設計判断・失敗パターン)は [docs/PROGRESS.md](docs/PROGRESS.md) に追記する.運用ルールは `.claude/rules/progress-log.md` を参照(該当ファイル編集時に自動ロードされる). ## 必須ルール(コード実装時) ### プロジェクト固有の規約(本体は `.claude/rules/` の path-scoped rule・該当ファイル編集時に自動ロード) -- **テスト方針**(`.claude/rules/testing.md`): ピュアロジック層は NUnit で必ず自動テスト,ハードウェア結合層(カメラ/UI/スレッド)は対象外としビルド+実機で担保.テストも **x64 必須**(`AnyCPU` はネイティブ DLL ロードで実行時に失敗) +- **テスト方針**(`.claude/rules/testing.md`): 4 層モデル.ピュアロジック・結合ロジック(`CameraBase`・`Form1` を偽カメラ+ヘッドレス Form1 で)・SDK アダプタから切り出した判断ロジックは NUnit で自動テストし,実機確認は SDK の実呼び出しと見た目だけに絞る(移行計画は PLAN_05).テストも **x64 必須**(`AnyCPU` はネイティブ DLL ロードで実行時に失敗) - **エラーハンドリング方針**(`.claude/rules/error-handling.md`): `ErrorMsg`+`bool` 返却・`ShowMessage`/`MessageBox`・`Debug.WriteLine` の既存パターンに従う.空 `catch` 禁止.**`Mat`・`Bitmap` 等のネイティブ資源は `using`/`try-finally` で確実に解放**(GC 任せはリーク→OOM/GDI 枯渇クラッシュ) - ※ コーディング規約(coding-style)・プロジェクト構成規則(project-structure)は**未整備**(GUIDE_01 の規約整備フェーズ参照) @@ -90,6 +90,7 @@ - プレビューメモリリーク修正計画: docs/03_PLAN/PLAN_03_プレビューメモリリーク修正計画.md - 性能評価計測計画: docs/03_PLAN/PLAN_04_性能評価計測計画.md +- テスト網羅性向上計画(4 層モデルへの移行): docs/03_PLAN/PLAN_05_テスト網羅性向上計画.md - ※ PLAN_01(リファクタリング)・PLAN_02(バグ修正)は完了につき削除済み(要点は docs/PROGRESS.md,経緯は git 履歴) ### 04_SPEC(仕様・設計) diff --git "a/docs/03_PLAN/PLAN_05_\343\203\206\343\202\271\343\203\210\347\266\262\347\276\205\346\200\247\345\220\221\344\270\212\350\250\210\347\224\273.md" "b/docs/03_PLAN/PLAN_05_\343\203\206\343\202\271\343\203\210\347\266\262\347\276\205\346\200\247\345\220\221\344\270\212\350\250\210\347\224\273.md" new file mode 100644 index 0000000..1c0b156 --- /dev/null +++ "b/docs/03_PLAN/PLAN_05_\343\203\206\343\202\271\343\203\210\347\266\262\347\276\205\346\200\247\345\220\221\344\270\212\350\250\210\347\224\273.md" @@ -0,0 +1,128 @@ +# テスト網羅性向上計画 (Test Coverage Improvement Plan) + +## 目的 (Purpose) + +人間による実機確認を「カメラ・光源 SDK の実呼び出し」と「実画像の見た目」だけに絞り,それ以外の回帰を自動テストで担保する. + +方針の本体(常に従う規約)は [testing.md](../../.claude/rules/testing.md) に置き,本書はそこへ移行するための段取り(フェーズ・完了条件・リスク)を定める. + +## 背景 (Background) + +- 2026-10-06 の `/auto-audit` では,報告した指摘 26 件の多くが「`CameraBase`・`Form1`・カメラ層で再現テストを書けない」ことを理由に自動修正できなかった.中でも保存失敗が成功扱いになる件や終了時の画像取りこぼしは,データ欠損に直結する. +- 旧方針は「ハードウェア結合層は自動テスト対象外」としていた.しかし実際には,偽カメラ(`SelfTest/SelfTestCamera`)とヘッドレス `Form1`(`Form1.SelfTest.cs`)がすでにあり,セルフテストでは `CameraBase` の校正〜保存を通している.NUnit で細かく検証していないだけで,自動化できない範囲は思っていたより狭い. +- 異常系(保存失敗・終了時・設定ファイル破損)に問題が集中していた.正常系の目視確認だけでは見つからない種類の不具合である. + +## 現状の手動確認の内訳 (Current Manual Checks) + +| 確認内容 | 本当に実機が要るか | 移行先 | +| --- | --- | --- | +| カメラ接続・露光/ゲインの反映・フレーム取得 | 要る(SDK の実呼び出し) | 実機に残す(層④) | +| 光源の ON/OFF | 要る(シリアル通信) | 実機に残す(層④) | +| 校正の自動開始〜完了,撮影ボタンの有効化 | 判断ロジックは不要 | 層②(偽カメラ+ヘッドレス Form1) | +| 単発・連続撮影でファイルが保存される | 不要 | 層②・セルフテスト | +| 保存失敗・ディスク満杯・設定破損時の挙動 | 不要(失敗を注入できる) | 層② | +| アプリを閉じてもエラーが出ない | 終了順序の判断は不要 | 層②(SDK の停止そのものは層④) | +| 校正結果・色の妥当性 | 合成チャートでは不十分 | 実機録画データによる回帰(層①の拡張) | + +## 目標とする 4 層モデル (Target Four-Layer Model) + +| 層 | 対象 | テスト手段 | +| --- | --- | --- | +| ① ピュアロジック | 色補正・チャート検出・CSV・設定・ゲイン計算・画像統計 | NUnit(実値で検証) | +| ② 結合ロジック | `CameraBase` の校正の状態遷移・撮影ループ・`SaveThread`・失敗通知・終了処理,`Form1` の UI 連携メソッド | NUnit+偽カメラ+ヘッドレス `Form1`(STA スレッドでメッセージを処理するハーネス) | +| ③ SDK アダプタ | `IScam`・`Lucam`・`LightSource` | 判断ロジックを純粋関数へ切り出して層①で検証する.SDK 呼び出しそのものは層④ | +| ④ 実機 | SDK の実呼び出し・実画像の見た目 | 短い手動チェックリスト(TEST_01) | + +詳細な判断基準は [testing.md](../../.claude/rules/testing.md) を参照する. + +## 決定事項 (Decisions) + +2026-10-06 にユーザーと合意した. + +| 項目 | 決定 | +| --- | --- | +| テストプロジェクトへの `System.Windows.Forms` 参照追加 | 許可する(.NET Framework 標準アセンブリで NuGet 依存は増えない) | +| 層②(`CameraBase` を NUnit で検証) | 行う | +| 層③(SDK アダプタからの判断ロジック切り出し) | 撮影・保存の信頼性修正(`/auto-audit` 報告の束 B)と同時に行う | +| 実機録画データ | 撮る.**舌診チャートと白板のみ**(舌の画像は個人情報のため扱わない) | +| カバレッジ | 数値目標は課さず,「一度も通っていない箇所」を見つける地図として使う | +| 実機録画データの置き場所 | **Git LFS** で管理する.CI が無くテストは開発マシンでしか回らないため,コードと同じ版のデータが clone で確実に手元に来る方式を選んだ.1 機種・1 場面あたり 3〜5 フレームに絞り,合計 100〜150MB 程度に抑える(GitHub の LFS 無料枠はストレージ 1GB・転送量 1GB/月) | +| 層②のテストの実行時間 | **層②全体で 30 秒を目安**とし,**1 件あたり 15 秒のタイムアウト**を付ける.超えそうなときはテスト用設定(校正フレーム数等)を見直して速くする.`/auto-audit` は修正 1 件ごとにテスト一式を何度も回すため,ここが延びると全作業が遅くなる | +| `/verify` に含めるか | **常に含める**(`run_selftest.py` は絞り込まずに全件実行するので追加設定は不要).CI が無く,`/verify`・`/auto-audit` が実質唯一の自動チェックのため,カテゴリで分けて通常は回さない運用にはしない | +| メモリ量で判定するテスト | 2026-10-06 に追加したリーク再現テスト等は `[Category("Memory")]` に分け,環境による不安定さが出たら通常の実行から外せるようにする | + +## フェーズ (Phases) + +### フェーズ 0: 方針の文書化 + +- 本書を作成し,[testing.md](../../.claude/rules/testing.md) を 4 層モデルに改訂する +- 完了条件: ユーザーのレビューを経て main に取り込む + +### フェーズ 1: 層②の基盤と試作 + +- テストプロジェクトに `System.Windows.Forms` の参照を追加する +- テスト用ハーネスを作る + - STA スレッドを立て,ヘッドレス `Form1` を生成してハンドルを作る(`SelfTestStart`) + - 条件が満たされるまでメッセージを処理する待機処理(タイムアウト必須).`SelfTestRunner` の `Application.DoEvents` による待機と同じ考え方で,可能なら共通化する + - 一時フォルダと一時 `config.xml` を用意する(`Config.Load(path)` を使う) +- テスト用の一時設定では校正フレーム数(`Calib/Frames`)等を小さくし,1 件あたり 1 秒前後で終わるようにする +- 既存のメモリ量判定テストに `[Category("Memory")]` を付ける +- 試作として「保存失敗が成功扱いになる」件(`/auto-audit` 報告台帳)の赤テストを書き,修正する +- 完了条件: + - ハーネスを使うテストが 3 回連続で安定して緑になる + - 層②の各テストに 15 秒のタイムアウトが付いている + - Release ビルドに影響しない(`SELFTEST` は Debug 系のみ) + +### フェーズ 2: 層②の拡充 + +- 次の観点を網羅する.各観点に**失敗を注入するケースを 1 つ以上**含める + - 校正の状態遷移(チャート検出 → 固定判定 → 変換行列算出 → 撮影ボタン有効化) + - 撮影ループ(単発・連続・枚数 0・間隔 0) + - `SaveThread`(保存失敗・例外時の後始末・残りフレームの破棄) + - 切断・終了の順序(保存中の終了,校正中の終了) + - `Form1` の UI 連携メソッド(UI スレッド外からの呼び出しを含む) +- `/auto-audit` 報告台帳のうち,層②で再現テストを書けるようになった項目をここで修正する +- 完了条件: 上記の各観点に正常系と失敗注入のテストがそろい,層②全体の実行時間が 30 秒の目安に収まる + +### フェーズ 3: 層③の切り出し + +- 撮影・保存の信頼性修正(束 B)と同時に,`IScam`・`Lucam`・`LightSource` から判断ロジックを純粋関数として切り出す + - 例: ROI とフレームサイズの照合,ホワイトバランスのゲイン算出,フレームバッファのサイズ検査,光源ポートを開けなかったときの扱い,例外を通知に変える処理 +- SDK を呼ぶ部分は「受け取って渡すだけ」の薄いアダプタに残す +- 完了条件: 切り出した関数に層①のテストがあり,切り出し直後に実機確認を 1 回行う + +### フェーズ 4: 実機録画データによる回帰 + +- 開始時に Git LFS を準備する(このマシンへの `git lfs` の導入確認,`.gitattributes` での対象パターン指定,GitHub 側の LFS の利用確認) +- 人間が実機(ImagingSource・Lumenera の両方)で舌診チャートと白板を撮影し,フレームを可逆形式(PNG)で保存する.1 機種・1 場面あたり 3〜5 フレームとする +- 録画フレームを繰り返し流して校正を通し,チャート検出のマスク位置,変換行列(基準 CSV との一致.許容誤差つき),変換後の色票の色差を自動で比較するテストを作る +- 完了条件: 両機種の録画データで回帰テストが緑になり,データが Git LFS で管理されている + +### フェーズ 5: 手動チェックリストの縮小とカバレッジの確認 + +- `Microsoft.CodeCoverage`(テストプロジェクトに導入済み)で未通過の箇所を確認し,重要な経路の抜けを埋める +- [TEST_01](../06_TEST/TEST_01_テスト仕様.md) の手動テストを下記の目標形に改訂する +- 完了条件: TEST_01 の改訂が main に取り込まれる + +## 手動確認の目標形 (Target Manual Checklist) + +フェーズ 5 完了後に TEST_01 に残す項目の想定. + +- 両機種(ImagingSource・Lumenera)でカメラが自動接続され,プレビューが出る +- 露光・ゲインの設定がプレビューに反映されている(目視) +- 実物の舌診チャートで自動校正が完了する +- 撮影した画像の色が目視で妥当である +- 光源が ON/OFF する +- 撮影中・保存中にアプリを閉じても固まらない(SDK の停止が絡むため最終確認として残す) + +※ 保存ファイルの有無・内容,失敗時の通知,撮影ボタンの有効/無効といった項目は層②とセルフテストに移し,手動では確認しない. + +## リスクと対策 (Risks) + +| リスク | 対策 | +| --- | --- | +| WinForms のテストがハング・不安定になる(STA・メッセージ処理の誤り) | ハーネスを 1 か所に集約し,待機には必ずタイムアウトを付ける.新しいハーネス利用テストは 3 回連続実行で安定性を確認してから取り込む | +| テストの実行時間が延びて `/verify` や `/auto-audit` が遅くなる | 層②のテストに `[Category("Integration")]` を付けて所要時間を計測し,全体 30 秒の目安を超えそうならテスト用設定を見直して速くする(`/verify` から外すことはしない) | +| テストが `Form1` の内部構造に依存して壊れやすくなる | `Form1.SelfTest.cs` のアクセサ(`SelfTestMessage`・`SelfTestLastError`・`SelfTestShotsEnabled` 等)経由でのみ観測する.足りないアクセサはそこに追加する | +| メモリ量で判定するテストが環境によって不安定になる | 2026-10-06 に追加したリーク再現テスト(プライベートメモリ増分のしきい値判定,OpenCV 内部の参照カウント読み取り)を棚卸しし,必要なら `[Category("Memory")]` に分ける | +| 実機録画データが大きい/個人情報を含む | 舌診チャートと白板に限る.フレーム数を絞り Git LFS で管理する.LFS の転送量の無料枠(1GB/月)を超えそうなら,データの削減か有料枠を検討する | diff --git a/docs/PROGRESS.md b/docs/PROGRESS.md index acabf36..c0525b9 100644 --- a/docs/PROGRESS.md +++ b/docs/PROGRESS.md @@ -162,3 +162,8 @@ - [x] **OpenCvSharp 4.13 の ArUco API 移行を main に取り込み(#31・PR #36)** (2026-09-29): 実機確認待ちで保留していた PR #36 に main(NUnit 4.6.1 移行・セルフテスト導入など 20 コミット)を取り込んで競合を解消し,実機確認を経てマージした.競合した依存設定は main 側を正とし,OpenCvSharp の 4.13 化(`lib/netstandard2.0` 参照・runtime の `.targets` 取り込み・`OpenCvSharp` のバインディングリダイレクト)だけを当て直した. - 失敗パターン: PR の保留中に main へ入ったセルフテスト(`SelfTestChart`・`SelfTestRunner`・`SelfTestChartTests`)が旧 API(`PredefinedDictionaryName`・`CvAruco.DetectMarkers`)で書かれており,競合解消だけではビルドが壊れる状態だった.4.10 と 4.13 の両方で動く書き方が無い API 移行を長く保留すると,その間に増えた旧 API の利用箇所も移行対象になる.取り込み時は `grep` で旧 API の残存を確認すること. - 後段: System.Drawing.Common 10 系への追従(#45)が可能になった. + +- [x] **テスト方針を 4 層モデルに改訂し,移行計画(PLAN_05)を策定** (2026-10-06): 人間の実機確認を「SDK の実呼び出し」と「実画像の見た目」だけに絞るため,`testing.md` を「ピュアロジック/ハードウェア結合」の 2 層から,ピュアロジック・結合ロジック・SDK アダプタ・実機の 4 層に改訂した.段取りは PLAN_05(フェーズ 0〜5)にまとめた. + - 動機: 同日の `/auto-audit` で報告した 26 件の多くが「`CameraBase`・`Form1`・カメラ層で再現テストを書けない」ことを理由に自動修正できなかった.保存失敗が成功扱いになる件など,データ欠損に直結するものも含まれていた.偽カメラとヘッドレス `Form1` はセルフテスト用にすでにあり,自動化できない範囲は思っていたより狭かった. + - 設計判断: テストプロジェクトへの `System.Windows.Forms` 参照の追加を許可した.`CameraBase` は偽カメラ+ヘッドレス `Form1` で検証し,カメラ SDK はモックせずに判断ロジックを切り出す(Humble Object).実機録画データ(チャート・白板のみ.舌は個人情報のため除外)は Git LFS で管理する.層②は全体 30 秒・1 件 15 秒を上限とし,CI が無いため `/verify` に常に含める. + - 後段: フェーズ 1(WinForms 参照の追加・STA ハーネスの作成・「保存失敗が成功扱い」の赤テストと修正)を `/implement` で行う.`fix/auto-audit` の取り込みは実機確認待ち.