Newer
Older
TIASShot / .claude / rules / testing.md

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

テスト方針 (Testing Policy)

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

基本方針 (Basic Policy)

本プロジェクトは実機カメラ(Lucam / ImagingSource)・光源・ネイティブライブラリ(OpenCvSharp)に強く依存する一方で,画像処理・色補正・設定読み込み等のハードウェアに依存しない純粋ロジックも含む.

目標は,人間による実機確認を「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 のフェーズに沿って整備中である.基盤が整うまでの間,層②・③に当たる変更は「後述の手段で書けるなら書く.書けないなら理由を明示して設計レビュー+セルフテスト+実機で担保する」とする.

標準に則る.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)
nullAssert.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
結合ロジックテスト層②自動(NUnit.[Category("Integration")])コミット前・/implement Phase 2
ビルドテストソリューション全体自動(MSBuild)全コミット前
回帰テスト色補正・チャート検出の出力(合成チャート,および実機で録画したチャート・白板のフレーム)自動(基準データ比較)リファクタ・修正の前後
セルフテスト依存ロード・偽カメラでのプレビュー経路・合成チャートでの校正〜保存自動(--selftest./verify・/implement Phase 1b)依存更新・層②〜④の変更後,手動テストの前段
手動テストSDK の実呼び出し・実画像の見た目(層④)手動(実機)機能追加・修正の完了時

※ 実機録画データ(舌診チャート・白板のフレーム)による回帰は PLAN_05 のフェーズ 4 で整備する.舌の画像は個人情報のためテストデータに含めない.

回帰テスト・手動テストの具体手順は TEST_01_テスト仕様 に記載する.

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

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

層①: ピュアロジック層

対象テスト内容の例
ColorCorrection/ColorCorrector次元拡張(ExtendMat)・変換行列算出・色変換の入出力.既知の入力 Mat に対する出力 Mat/数値の検証
ChartDetection/ChartDetector既知のチャート画像を入力したときのマスク位置・検出結果の幾何的検証
Config設定 XML のパース・型変換・範囲フィルタ(例: チャネル数の 4〜17 絞り込み)
Utils/IoUtilCSV の読み書き往復(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 スレッド外から呼んだときのマーシャリング

自動テスト対象外(層④: 実機層)

対象対象外の理由
IScam・Lucam の SDK 呼び出し(接続・露光/ゲイン設定・フレーム取得・ストリーム停止)カメラ SDK(dll.* / TIS.Imaging.*)と実機に依存する.判断ロジックは層③として切り出して層①で検証する
LightSource のシリアル通信光源(実機)に依存する.ポートを開けなかったときの扱い等の判断は層③として切り出す
画面描画の見た目(Form1・PreviewMonitor の表示内容)目視でしか判断できない.表示用 Bitmap の所有権・更新の流れはセルフテスト(preview)で担保する

※ 層④に手を入れた場合は設計レビュー+実機回帰で挙動を確認する.その前段として,実機なしで通せる範囲はセルフテスト(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 スレッドとメッセージ処理を必要とする.テストは共通ハーネス(TIASshot.Tests/IntegrationHarness.cs.使用例は CameraBaseSaveIntegrationTests)を通して,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)

  • フレームワーク: NUnit(NUnit3TestAdapter で Visual Studio / dotnet test から実行).
  • テストプロジェクト: TIASshot.Tests(x64 / .NET Framework 4.8.本体と同一ターゲット).
  • ネイティブ依存: ColorCorrector 等のテストは OpenCvSharp のネイティブ DLL をロードするため,テストプロジェクトも x64 必須かつ OpenCvSharp ランタイムを参照すること(プラットフォームを AnyCPU にすると Mat 演算が実行時に失敗する).
  • 配置: ソリューションルート直下にテストプロジェクトを置く.
TIASshot/                  ← ソリューションルート
├── TIASshot.sln
├── TIASshot/             ← 本体プロジェクト
└── TIASshot.Tests/       ← テストプロジェクト(NUnit)
    ├── ColorCorrectorTests.cs
    ├── ConfigTests.cs
    └── ...
  • 命名: テストクラスは <対象クラス>Tests,テストメソッドは「対象_条件_期待結果」が分かる名前にする(日本語名も可).
  • 粒度: 1 テストにつき 1 つの検証観点とする.1 メソッドで多数の Assert を詰め込まない.
  • テストデータ: 入力画像・基準 CSV 等は TIASshot.Tests 配下の専用フォルダ(例: TestData/)に置き,テストから相対参照する.
    • 実機録画フレーム(舌診チャート・白板)は Git LFS で管理する.可逆形式(PNG)で保存し,1 機種・1 場面あたり 3〜5 フレームに絞る.校正にはこれを繰り返し流して使う
    • 舌の画像はテストデータに含めない(個人情報のため)

テストの実行 (Running Tests)

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

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

エージェント(tester・refactorer 等)は ENV_04 の「テストランナー」(python scripts/verify/run_tests.py)を使う.ビルドからテスト実行・結果の集計までを 1 コマンドで行い,--filter(カテゴリ等の絞り込み)と --repeat(安定性確認の連続実行)に対応する.オーケストレーターにテストの実行を代行させない.

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

モック方針 (Mocking)

  • 過剰なモックを避ける.ピュアロジックは実値(実際の Mat・数値)で検証する.
  • 静的依存の扱い: Config のような静的クラスへの依存がテストの妨げになる場合,テスト容易化のための最小限のシーム(引数化・依存の注入)を導入してよい.ただし本番の挙動は変えないこと.
  • ハードウェア SDK はモックしない.SDK の型や関数を偽物に差し替えてテストするのではなく,判断ロジックを SDK から切り出してテストする(層③).
  • 偽カメラはモックではなく代替実装として使う.SelfTestCamera は CameraBase の派生として実際の校正・撮影・保存の流れを通すもので,SDK を模倣するものではない.失敗を注入したい場合は,偽カメラ(またはテスト用の派生クラス)に失敗させる口を足してよい.ただし本番のコード(IScam・Lucam)の挙動は変えない.

カバレッジ方針 (Coverage)

  • 数値的なカバレッジノルマは課さない.カバレッジ率の達成自体を目的化しない.
  • 代わりに,重要な経路を網羅することを定性的な目標とする.具体的には,色変換行列の算出・次元拡張・CSV 往復・ゲイン更新比率といった「誤ると出力が壊れる中核ロジック」と,校正・撮影・保存の流れ(正常系と失敗時)にテストを行き渡らせる.
  • カバレッジは抜けを見つける地図として使う.テストプロジェクトに導入済みの Microsoft.CodeCoverage で「一度も通っていない箇所」を確認し,そこが重要な経路なら優先してテストを足す.

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

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