本プロジェクトのテストの種類・粒度・対象範囲・実行タイミングを定める. 具体的な回帰テスト手順は TEST_01_テスト仕様 を参照する(本 rule は方針,TEST_01 は具体手順という役割分担).
本プロジェクトは実機カメラ(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)に従い,テストは実装のなぞり書きではなく仕様(期待される入出力)を基準に書く.
制約モデル(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(...) を落とさないこと.厳密比較になりフレーキーになる.
| 種類 | 対象 | 自動化 | 実行タイミング |
|---|---|---|---|
| 単体テスト | 層①(層③から切り出した判断ロジックを含む) | 自動(NUnit) | コミット前・/implement Phase 2 |
| 結合ロジックテスト | 層② | 自動(NUnit.[Category("Integration")]) | コミット前・/implement Phase 2 |
| ビルドテスト | ソリューション全体 | 自動(MSBuild) | 全コミット前 |
| 回帰テスト | 色補正・チャート検出の出力(合成チャート,および実機で録画したチャート・白板のフレーム) | 自動(基準データ比較) | リファクタ・修正の前後 |
| セルフテスト | 依存ロード・偽カメラでのプレビュー経路・合成チャートでの校正〜保存 | 自動(--selftest./verify・/implement Phase 1b) | 依存更新・層②〜④の変更後,手動テストの前段 |
| 手動テスト | SDK の実呼び出し・実画像の見た目(層④) | 手動(実機) | 機能追加・修正の完了時 |
※ 実機録画データ(舌診チャート・白板のフレーム)による回帰は PLAN_05 のフェーズ 4 で整備する.舌の画像は個人情報のためテストデータに含めない.
回帰テスト・手動テストの具体手順は TEST_01_テスト仕様 に記載する.
どのクラス・処理を自動テストの対象とするかを明示する.新しいコードを追加した際は,この表に従って対象/対象外を判断する.
| 対象 | テスト内容の例 |
|---|---|
ColorCorrection/ColorCorrector | 次元拡張(ExtendMat)・変換行列算出・色変換の入出力.既知の入力 Mat に対する出力 Mat/数値の検証 |
ChartDetection/ChartDetector | 既知のチャート画像を入力したときのマスク位置・検出結果の幾何的検証 |
Config | 設定 XML のパース・型変換・範囲フィルタ(例: チャネル数の 4〜17 絞り込み) |
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 スレッド外から呼んだときのマーシャリング |
| 対象 | 対象外の理由 |
|---|---|
IScam・Lucam の SDK 呼び出し(接続・露光/ゲイン設定・フレーム取得・ストリーム停止) | カメラ SDK(dll.* / TIS.Imaging.*)と実機に依存する.判断ロジックは層③として切り出して層①で検証する |
LightSource のシリアル通信 | 光源(実機)に依存する.ポートを開けなかったときの扱い等の判断は層③として切り出す |
画面描画の見た目(Form1・PreviewMonitor の表示内容) | 目視でしか判断できない.表示用 Bitmap の所有権・更新の流れはセルフテスト(preview)で担保する |
※ 層④に手を入れた場合は設計レビュー+実機回帰で挙動を確認する.その前段として,実機なしで通せる範囲はセルフテスト(python scripts/verify/run_selftest.py..claude/verify-profile.md)で確認できる./implement の Tester フェーズでは,対象が層④だけの場合に限り,テストを書かずに設計レビューで判断してよい.層②・③に当たる部分が含まれるなら,その部分のテストは書く.
層②のテストは,カメラの代わりに偽カメラ(SelfTest/SelfTestCamera.CameraBase の派生)を,フォームの代わりにヘッドレス Form1(Form1.SelfTest.cs)を使う.どちらも SELFTEST フラグ(Debug 系の構成のみ)でコンパイルされるため,層②のテストも #if SELFTEST で囲む.
Control.Invoke は STA スレッドとメッセージ処理を必要とする.テストは共通ハーネス(PLAN_05 フェーズ 1 で作成)を通して,STA スレッド上でヘッドレス Form1 を生成し,SelfTestStart でハンドルを作ってから使う.ハーネスを経由せずに個別のテストでスレッドやメッセージ処理を組まないCalib/Frames)等を小さくし,1 件あたり 1 秒前後で終わるようにする.目安を超えそうなときは,テストを減らしたり /verify から外したりせず,テスト用設定を見直して速くする(/auto-audit は修正 1 件ごとにテスト一式を何度も回すため,ここが延びると全作業が遅くなる)Form1.SelfTest.cs のアクセサ(SelfTestMessage・SelfTestLastError・SelfTestShotsEnabled 等)と,保存されたファイルで確かめる.Form1 の private フィールドをリフレクションで読まない.足りないアクセサは Form1.SelfTest.cs に追加するconfig.xml を Config.Load(path) で読み込み,保存先も一時フォルダにする.同梱の config.xml や本番の保存先(C:\TIAS_Data 等)には書き込まない.テストの終わりに一時フォルダを削除する[Category("Integration")] を付ける(所要時間の把握のため).メモリ量で判定するテスト(リーク再現・ベンチ)には [Category("Memory")] を付ける(環境による不安定さが出たときに通常の実行から外せるようにするため)/verify(run_selftest.py)・/auto-audit・/implement のテスト実行に常に含める.CI が無く,これらが実質唯一の自動チェックのため,カテゴリで分けて普段は回さない運用にはしない層③(IScam・Lucam・LightSource)に手を入れるときは,次の形に寄せる(Humble Object パターン).
GainCalc のように),フレームバッファのサイズ検査,ポートを開けなかったときの扱い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,テストメソッドは「対象_条件_期待結果」が分かる名前にする(日本語名も可).Assert を詰め込まない.TIASshot.Tests 配下の専用フォルダ(例: TestData/)に置き,テストから相対参照する.
テストプロジェクトは本体に合わせたレガシー形式(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 にすること).
Config のような静的クラスへの依存がテストの妨げになる場合,テスト容易化のための最小限のシーム(引数化・依存の注入)を導入してよい.ただし本番の挙動は変えないこと.SelfTestCamera は CameraBase の派生として実際の校正・撮影・保存の流れを通すもので,SDK を模倣するものではない.失敗を注入したい場合は,偽カメラ(またはテスト用の派生クラス)に失敗させる口を足してよい.ただし本番のコード(IScam・Lucam)の挙動は変えない.Microsoft.CodeCoverage で「一度も通っていない箇所」を確認し,そこが重要な経路なら優先してテストを足す..claude/rules/testing.md)はテストの方針を定める.docs/06_TEST/ に追記する(本 rule には方針のみ残し,個別ケースは重複させない).