diff --git a/.gitignore b/.gitignore
index 4d1b627..35a24fa 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,8 +6,8 @@
*.user
# ビルド出力
-TIASshot/bin/
-TIASshot/obj/
+bin/
+obj/
# NuGet パッケージ
packages/
diff --git a/CLAUDE.md b/CLAUDE.md
index 8b1424e..7918e2d 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -4,7 +4,7 @@
## 開発進捗
-最新: 規約整備としてテスト方針(GUIDE_08)・エラーハンドリング方針(GUIDE_09)を追加
+最新: テストプロジェクト(TIASshot.Tests, NUnit/x64)を立ち上げ,ピュアロジック層(ColorCorrector/Config/IoUtil)に初期テストを追加
※ 本欄は**最新ステップ 1 行のみ上書き更新**.詳細な進捗履歴(動機・設計判断・失敗パターン)は [docs/PROGRESS.md](docs/PROGRESS.md) に追記する.運用ルールは GUIDE_05「進捗記録の運用ルール(CLAUDE.md / PROGRESS.md)」を参照.
## 必須ルール(コード実装時)
diff --git a/TIASshot.Tests/ColorCorrectorTests.cs b/TIASshot.Tests/ColorCorrectorTests.cs
new file mode 100644
index 0000000..e0dfe91
--- /dev/null
+++ b/TIASshot.Tests/ColorCorrectorTests.cs
@@ -0,0 +1,290 @@
+using NUnit.Framework;
+using OpenCvSharp;
+
+namespace TIASshot.Tests {
+ ///
+ /// ColorCorrector のテスト
+ ///
+ [TestFixture]
+ public class ColorCorrectorTests {
+
+ // ---------------------------------------------------------------
+ // ExtendMat テスト
+ // ---------------------------------------------------------------
+
+ ///
+ /// ExtendMat: 3列 Mat(Nx3)を channels=4 で拡張すると列数が 4 になる
+ ///
+ [Test]
+ public void ExtendMat_3ColsTo4Channels_ResultHas4Cols() {
+ // Arrange: 2行 3列 (CV_64FC1)
+ using (var src = new Mat(2, 3, MatType.CV_64FC1)) {
+ // row=0: B=10, G=20, R=30 (Col0=B, Col1=G, Col2=R)
+ SetBGR(src, row: 0, b: 10.0, g: 20.0, r: 30.0);
+ SetBGR(src, row: 1, b: 0.0, g: 0.0, r: 0.0);
+
+ var corrector = MakeCorrectorWith4Channels();
+
+ // Act
+ using (var dst = corrector.ExtendMat(src, channels: 4)) {
+ // Assert
+ Assert.AreEqual(2, dst.Rows, "行数は変わらない");
+ Assert.AreEqual(4, dst.Cols, "列数は指定した channels 数になる");
+ }
+ }
+ }
+
+ ///
+ /// ExtendMat: channels=4 の 1 列目(i=0)は定数 1.0(ExtendChannels[0]={3,3,3} → vals[3]*vals[3]*vals[3]=1)
+ ///
+ [Test]
+ public void ExtendMat_4Channels_FirstColumnIsConstant1() {
+ // Arrange
+ using (var src = new Mat(3, 3, MatType.CV_64FC1)) {
+ SetBGR(src, row: 0, b: 10.0, g: 20.0, r: 30.0);
+ SetBGR(src, row: 1, b: 5.0, g: 15.0, r: 50.0);
+ SetBGR(src, row: 2, b: 0.0, g: 0.0, r: 0.0);
+
+ var corrector = MakeCorrectorWith4Channels();
+
+ // Act
+ using (var dst = corrector.ExtendMat(src, channels: 4)) {
+ // Assert: 列 0 は全行で 1.0
+ for (int row = 0; row < dst.Rows; row++) {
+ Assert.AreEqual(1.0, dst.At(row, 0), 1e-10,
+ $"row={row}: 列0(定数項)は 1.0 であること");
+ }
+ }
+ }
+ }
+
+ ///
+ /// ExtendMat: channels=4 の 2~4 列目は R, G, B の値(ExtendChannels[1]={0,3,3}→R, [2]={1,3,3}→G, [3]={2,3,3}→B)
+ ///
+ [Test]
+ public void ExtendMat_4Channels_Columns1To3AreRGB() {
+ // Arrange: B=10, G=20, R=30
+ using (var src = new Mat(1, 3, MatType.CV_64FC1)) {
+ SetBGR(src, row: 0, b: 10.0, g: 20.0, r: 30.0);
+
+ var corrector = MakeCorrectorWith4Channels();
+
+ // Act
+ using (var dst = corrector.ExtendMat(src, channels: 4)) {
+ // Assert
+ // ExtendChannels[1]={0,3,3}: vals[0]*vals[3]*vals[3] = R*1*1 = 30
+ Assert.AreEqual(30.0, dst.At(0, 1), 1e-10, "列1 は R");
+ // ExtendChannels[2]={1,3,3}: vals[1]*vals[3]*vals[3] = G*1*1 = 20
+ Assert.AreEqual(20.0, dst.At(0, 2), 1e-10, "列2 は G");
+ // ExtendChannels[3]={2,3,3}: vals[2]*vals[3]*vals[3] = B*1*1 = 10
+ Assert.AreEqual(10.0, dst.At(0, 3), 1e-10, "列3 は B");
+ }
+ }
+ }
+
+ ///
+ /// ExtendMat: channels=5 の列4(i=4)は R*G(ExtendChannels[4]={0,1,3}: vals[0]*vals[1]*vals[3]=R*G*1)
+ ///
+ [Test]
+ public void ExtendMat_5Channels_Column4IsRxG() {
+ // Arrange: B=10, G=20, R=30
+ using (var src = new Mat(1, 3, MatType.CV_64FC1)) {
+ SetBGR(src, row: 0, b: 10.0, g: 20.0, r: 30.0);
+
+ var corrector = new ColorCorrector(null, null, new int[] { 5 });
+
+ // Act
+ using (var dst = corrector.ExtendMat(src, channels: 5)) {
+ // Assert: 列4(i=4)= R*G = 30*20 = 600
+ Assert.AreEqual(600.0, dst.At(0, 4), 1e-10, "列4 は R*G = 30*20 = 600");
+ }
+ }
+ }
+
+ ///
+ /// ExtendMat: Cols が 3 以外の Mat はそのまま返す(スルー条件)
+ ///
+ [Test]
+ public void ExtendMat_NonStandardInput_ReturnsSrc() {
+ // Arrange: 2x5 → Cols*Channels() = 5*1 != 3 なのでスルー
+ using (var src = new Mat(2, 5, MatType.CV_64FC1)) {
+ var corrector = MakeCorrectorWith4Channels();
+
+ // Act
+ var dst = corrector.ExtendMat(src, channels: 4);
+
+ // Assert: 列数が変わらない(src のまま返る)
+ Assert.AreEqual(5, dst.Cols, "3列でない入力はそのまま返す");
+ // dst == src の参照なので Dispose しない(src の using で解放される)
+ }
+ }
+
+ // ---------------------------------------------------------------
+ // CalcConvertMatrix テスト
+ // ---------------------------------------------------------------
+
+ ///
+ /// CalcConvertMatrix: 出力行列のサイズは channels x 3 になる(channels=4 のケース)
+ ///
+ [Test]
+ public void CalcConvertMatrix_4Channels_OutputSizeIs4x3() {
+ // Arrange: 24行 3列(チャート 24 色票相当の最小入力)
+ using (var src = CreateLinearColorChart(rows: 24))
+ using (var target = CreateScaledColorChart(rows: 24, scale: 1.1)) {
+ var corrector = MakeCorrectorWith4Channels();
+
+ // Act
+ using (var conv = corrector.CalcConvertMatrix(src, target, channels: 4)) {
+ // Assert: 変換行列は channels x 3 = 4 x 3
+ Assert.AreEqual(4, conv.Rows, "行数(channels)は 4 であること");
+ Assert.AreEqual(3, conv.Cols, "列数は 3(RGB チャネル)であること");
+ }
+ }
+ }
+
+ ///
+ /// CalcConvertMatrix: 出力行列のサイズは channels x 3 になる(channels=6 のケース)
+ ///
+ [Test]
+ public void CalcConvertMatrix_6Channels_OutputSizeIs6x3() {
+ // Arrange
+ using (var src = CreateLinearColorChart(rows: 24))
+ using (var target = CreateScaledColorChart(rows: 24, scale: 0.9)) {
+ var corrector = new ColorCorrector(null, null, new int[] { 6 });
+
+ // Act
+ using (var conv = corrector.CalcConvertMatrix(src, target, channels: 6)) {
+ // Assert
+ Assert.AreEqual(6, conv.Rows, "行数(channels)は 6 であること");
+ Assert.AreEqual(3, conv.Cols, "列数は 3(RGB チャネル)であること");
+ }
+ }
+ }
+
+ // ---------------------------------------------------------------
+ // ConvertColor テスト
+ // ---------------------------------------------------------------
+
+ ///
+ /// ConvertColor: 変換行列が恒等行列(上位 3 行が単位行列・残りはゼロ)のとき出力は入力と一致する
+ ///
+ [Test]
+ public void ConvertColor_IdentityLikeMatrix_OutputMatchesInput() {
+ // Arrange: 入力 1行 3列(B=10, G=20, R=30)
+ using (var src = new Mat(1, 3, MatType.CV_64FC1))
+ using (var conv = CreateIdentityLikeConvMat()) {
+ SetBGR(src, row: 0, b: 10.0, g: 20.0, r: 30.0);
+
+ var corrector = MakeCorrectorWith4Channels();
+
+ // Act
+ // channels=4 の conv は 4x3。
+ // ExtendMat(src, 4) = [1, R, G, B] = [1, 30, 20, 10]
+ // conv の行 1,2,3 を R→R, G→G, B→B に対応させると出力が元に戻る。
+ // 行0(定数項)=0, 行1(R)=[1,0,0], 行2(G)=[0,1,0], 行3(B)=[0,0,1]
+ using (var dst = corrector.ConvertColor(src, conv)) {
+ // Assert: 出力は 1x3, 値は B=10, G=20, R=30
+ Assert.AreEqual(1, dst.Rows, "出力行数は入力と同じ");
+ Assert.AreEqual(3, dst.Cols, "出力列数は 3(RGB)");
+ Assert.AreEqual(10.0, dst.At(0, 0), 1e-10, "出力 B = 10");
+ Assert.AreEqual(20.0, dst.At(0, 1), 1e-10, "出力 G = 20");
+ Assert.AreEqual(30.0, dst.At(0, 2), 1e-10, "出力 R = 30");
+ }
+ }
+ }
+
+ ///
+ /// ConvertColor: 出力行列のサイズは入力の行数 x 3 になる(多行入力)
+ ///
+ [Test]
+ public void ConvertColor_MultipleRows_OutputSizeMatchesInput() {
+ // Arrange: 5行 3列
+ using (var src = new Mat(5, 3, MatType.CV_64FC1)) {
+ for (int row = 0; row < 5; row++) {
+ src.At(row, 0) = row * 10.0; // B
+ src.At(row, 1) = row * 10.0 + 5; // G
+ src.At(row, 2) = row * 10.0 + 8; // R
+ }
+ // 4x3 のゼロ以外の適当な変換行列(解が存在すればよい)
+ using (var conv = new Mat(4, 3, MatType.CV_64FC1)) {
+ conv.At(0, 0) = 0.5; conv.At(0, 1) = 0.0; conv.At(0, 2) = 0.0;
+ conv.At(1, 0) = 0.0; conv.At(1, 1) = 0.5; conv.At(1, 2) = 0.0;
+ conv.At(2, 0) = 0.0; conv.At(2, 1) = 0.0; conv.At(2, 2) = 0.5;
+ conv.At(3, 0) = 0.0; conv.At(3, 1) = 0.0; conv.At(3, 2) = 0.0;
+
+ var corrector = MakeCorrectorWith4Channels();
+
+ // Act
+ using (var dst = corrector.ConvertColor(src, conv)) {
+ // Assert: 出力は 5行 3列
+ Assert.AreEqual(5, dst.Rows, "出力行数は入力行数と同じ");
+ Assert.AreEqual(3, dst.Cols, "出力列数は 3(RGB)");
+ }
+ }
+ }
+ }
+
+ // ---------------------------------------------------------------
+ // ヘルパー
+ // ---------------------------------------------------------------
+
+ /// channels=4 の ColorCorrector を生成する
+ private static ColorCorrector MakeCorrectorWith4Channels()
+ => new ColorCorrector(null, null, new int[] { 4 });
+
+ ///
+ /// 1行の BGRカラム順 Mat に B/G/R 値をセットする
+ /// (ColorCorrector の内部配列は Col0=B, Col1=G, Col2=R)
+ ///
+ private static void SetBGR(Mat m, int row, double b, double g, double r) {
+ m.At(row, 0) = b;
+ m.At(row, 1) = g;
+ m.At(row, 2) = r;
+ }
+
+ ///
+ /// 線形スケールで値を増加させる 24行 3列の色票 Mat を生成する(CalcConvertMatrix 用)
+ /// row i の値: B=i+1, G=(i+1)*2, R=(i+1)*3
+ ///
+ private static Mat CreateLinearColorChart(int rows) {
+ var m = new Mat(rows, 3, MatType.CV_64FC1);
+ for (int row = 0; row < rows; row++) {
+ m.At(row, 0) = row + 1; // B
+ m.At(row, 1) = (row + 1) * 2; // G
+ m.At(row, 2) = (row + 1) * 3; // R
+ }
+ return m;
+ }
+
+ ///
+ /// の値に係数 scale をかけた色票 Mat を生成する
+ ///
+ private static Mat CreateScaledColorChart(int rows, double scale) {
+ var m = new Mat(rows, 3, MatType.CV_64FC1);
+ for (int row = 0; row < rows; row++) {
+ m.At(row, 0) = (row + 1) * 1.0 * scale; // B
+ m.At(row, 1) = (row + 1) * 2.0 * scale; // G
+ m.At(row, 2) = (row + 1) * 3.0 * scale; // R
+ }
+ return m;
+ }
+
+ ///
+ /// 恒等変換に相当する 4x3 変換行列を生成する
+ /// ExtendMat col順: [0]=定数1, [1]=R, [2]=G, [3]=B → 出力列: [0]=B, [1]=G, [2]=R
+ /// 行0(定数項)=0, 行1(R)→出力R=[0,0,1], 行2(G)→出力G=[0,1,0], 行3(B)→出力B=[1,0,0]
+ ///
+ private static Mat CreateIdentityLikeConvMat() {
+ var conv = new Mat(4, 3, MatType.CV_64FC1);
+ // 行0: 定数項が出力に寄与しない(0ベクトル)
+ conv.At(0, 0) = 0.0; conv.At(0, 1) = 0.0; conv.At(0, 2) = 0.0;
+ // 行1: R → 出力 R 列 (col2)
+ conv.At(1, 0) = 0.0; conv.At(1, 1) = 0.0; conv.At(1, 2) = 1.0;
+ // 行2: G → 出力 G 列 (col1)
+ conv.At(2, 0) = 0.0; conv.At(2, 1) = 1.0; conv.At(2, 2) = 0.0;
+ // 行3: B → 出力 B 列 (col0)
+ conv.At(3, 0) = 1.0; conv.At(3, 1) = 0.0; conv.At(3, 2) = 0.0;
+ return conv;
+ }
+ }
+}
diff --git a/TIASshot.Tests/ConfigTests.cs b/TIASshot.Tests/ConfigTests.cs
new file mode 100644
index 0000000..538f91c
--- /dev/null
+++ b/TIASshot.Tests/ConfigTests.cs
@@ -0,0 +1,107 @@
+using System;
+using System.IO;
+using NUnit.Framework;
+
+namespace TIASshot.Tests {
+ ///
+ /// Config のテスト(XML パース・型変換・存在しないキーの例外)
+ ///
+ [TestFixture]
+ public class ConfigTests {
+
+ private string _tempConfigPath;
+
+ // テスト用 XML の期待値定数
+ private const float ExpectedFloat = 1.5f;
+ private const int ExpectedInt = 42;
+ private const string ExpectedString = "hello";
+ private const string ExpectedNested = "deep";
+
+ private const string TestConfigXml =
+ "" +
+ "" +
+ " 1.5" +
+ " 42" +
+ " hello" +
+ " deep" +
+ "";
+
+ [SetUp]
+ public void SetUp() {
+ _tempConfigPath = Path.GetTempFileName();
+ // 拡張子を .xml に変更(XmlDocument.Load は拡張子を問わないが明示するために rename)
+ var xmlPath = _tempConfigPath + ".xml";
+ File.Move(_tempConfigPath, xmlPath);
+ _tempConfigPath = xmlPath;
+
+ File.WriteAllText(_tempConfigPath, TestConfigXml, System.Text.Encoding.UTF8);
+
+ Config.Load(_tempConfigPath);
+ }
+
+ [TearDown]
+ public void TearDown() {
+ if (File.Exists(_tempConfigPath)) File.Delete(_tempConfigPath);
+ }
+
+ ///
+ /// GetFloat でフロート値が正しく取得できる
+ ///
+ [Test]
+ public void GetFloat_存在するキー_正しい値を返す() {
+ var value = Config.GetFloat("FloatVal");
+ Assert.AreEqual(ExpectedFloat, value, 1e-6f);
+ }
+
+ ///
+ /// GetInt で整数値が正しく取得できる
+ ///
+ [Test]
+ public void GetInt_存在するキー_正しい値を返す() {
+ var value = Config.GetInt("IntVal");
+ Assert.AreEqual(ExpectedInt, value);
+ }
+
+ ///
+ /// GetString で文字列値が正しく取得できる
+ ///
+ [Test]
+ public void GetString_存在するキー_正しい値を返す() {
+ var value = Config.GetString("StrVal");
+ Assert.AreEqual(ExpectedString, value);
+ }
+
+ ///
+ /// 存在しないキーを指定したとき ArgumentException がスローされる
+ ///
+ [Test]
+ public void GetFloat_存在しないキー_ArgumentExceptionをスロー() {
+ Assert.Throws(() => Config.GetFloat("NoSuchKey"));
+ }
+
+ ///
+ /// 存在しないキーを GetInt で指定したとき ArgumentException がスローされる
+ ///
+ [Test]
+ public void GetInt_存在しないキー_ArgumentExceptionをスロー() {
+ Assert.Throws(() => Config.GetInt("NoSuchKey"));
+ }
+
+ ///
+ /// 存在しないキーを GetString で指定したとき ArgumentException がスローされる
+ ///
+ [Test]
+ public void GetString_存在しないキー_ArgumentExceptionをスロー() {
+ Assert.Throws(() => Config.GetString("NoSuchKey"));
+ }
+
+ ///
+ /// ネストした要素(Nested/Child)を XPath スラッシュ区切りで取得できる
+ ///
+ [Test]
+ public void GetString_ネスト要素_正しい値を返す() {
+ var value = Config.GetString("Nested/Child");
+ Assert.AreEqual(ExpectedNested, value);
+ }
+ }
+}
diff --git a/TIASshot.Tests/IoUtilTests.cs b/TIASshot.Tests/IoUtilTests.cs
new file mode 100644
index 0000000..5ff291e
--- /dev/null
+++ b/TIASshot.Tests/IoUtilTests.cs
@@ -0,0 +1,102 @@
+using System.IO;
+using NUnit.Framework;
+using OpenCvSharp;
+
+namespace TIASshot.Tests {
+ ///
+ /// IoUtil のテスト(CSV 読み書き往復でネイティブ DLL ロードも検証)
+ ///
+ [TestFixture]
+ public class IoUtilTests {
+
+ private string _csvPath;
+
+ [SetUp]
+ public void SetUp() {
+ _csvPath = Path.GetTempFileName();
+ }
+
+ [TearDown]
+ public void TearDown() {
+ if (File.Exists(_csvPath)) File.Delete(_csvPath);
+ }
+
+ ///
+ /// 2x3 の CV_64FC1 Mat を CSV に保存して再読込したとき、各要素が ±1e-6 以内で一致する
+ ///
+ [Test]
+ public void SaveAndLoad_2x3Mat_ValuesMatch() {
+ // Arrange
+ using (var src = CreateMat2x3()) {
+ // Act
+ IoUtil.SaveMatToCsv(_csvPath, src);
+ using (var loaded = IoUtil.LoadMatFromCsv(_csvPath)) {
+ // Assert
+ Assert.IsNotNull(loaded, "ロード結果が null であってはならない");
+ Assert.AreEqual(src.Rows, loaded.Rows, "行数が一致すること");
+ Assert.AreEqual(src.Cols, loaded.Cols, "列数が一致すること");
+
+ for (int row = 0; row < src.Rows; row++) {
+ for (int col = 0; col < src.Cols; col++) {
+ Assert.AreEqual(
+ src.At(row, col),
+ loaded.At(row, col),
+ 1e-6,
+ $"[{row},{col}] の値が ±1e-6 以内で一致すること");
+ }
+ }
+ }
+ }
+ }
+
+ ///
+ /// 1x1 の Mat(スカラー)を CSV に保存して再読込しても値が保たれる
+ ///
+ [Test]
+ public void SaveAndLoad_1x1Mat_ValuePreserved() {
+ // Arrange
+ const double Pi = 3.14159265358979;
+ using (var src = new Mat(1, 1, MatType.CV_64FC1)) {
+ src.At(0, 0) = Pi;
+
+ // Act
+ IoUtil.SaveMatToCsv(_csvPath, src);
+ using (var loaded = IoUtil.LoadMatFromCsv(_csvPath)) {
+ // Assert
+ Assert.IsNotNull(loaded);
+ Assert.AreEqual(Pi, loaded.At(0, 0), 1e-6);
+ }
+ }
+ }
+
+ ///
+ /// 存在しないファイルをロードしたとき null を返す
+ ///
+ [Test]
+ public void LoadMatFromCsv_NonExistentFile_ReturnsNull() {
+ // Act
+ var result = IoUtil.LoadMatFromCsv("does_not_exist_xyz.csv");
+
+ // Assert
+ Assert.IsNull(result, "存在しないファイルのロードは null を返すこと");
+ }
+
+ // ---------------------------------------------------------------
+ // ヘルパー
+ // ---------------------------------------------------------------
+
+ ///
+ /// 代表的な 2x3 CV_64FC1 Mat を生成する
+ ///
+ private static Mat CreateMat2x3() {
+ var m = new Mat(2, 3, MatType.CV_64FC1);
+ m.At(0, 0) = 1.234567;
+ m.At(0, 1) = -2.345678;
+ m.At(0, 2) = 0.000001;
+ m.At(1, 0) = 100.0;
+ m.At(1, 1) = -0.5;
+ m.At(1, 2) = 999.999999;
+ return m;
+ }
+ }
+}
diff --git a/TIASshot.Tests/TIASshot.Tests.csproj b/TIASshot.Tests/TIASshot.Tests.csproj
new file mode 100644
index 0000000..3e5f4d6
--- /dev/null
+++ b/TIASshot.Tests/TIASshot.Tests.csproj
@@ -0,0 +1,72 @@
+
+
+
+
+
+
+ Debug
+ x64
+ {6EEFE514-C4D9-4F71-9A8A-06F9748361CE}
+ Library
+ TIASshot.Tests
+ TIASshot.Tests
+ v4.8
+ x64
+ 512
+
+
+
+
+ true
+ bin\x64\Debug\
+ DEBUG;TRACE
+ full
+ x64
+ 7.3
+ prompt
+
+
+ bin\x64\Release\
+ TRACE
+ true
+ pdbonly
+ x64
+ 7.3
+ prompt
+
+
+
+ ..\packages\NUnit.3.14.0\lib\net45\nunit.framework.dll
+
+
+ ..\packages\OpenCvSharp4.4.10.0.20241108\lib\net48\OpenCvSharp.dll
+
+
+
+
+
+
+
+
+
+ {1D3B69FE-D6B4-42B9-BBCC-B8CB9CF3DE4F}
+ TIASshot
+
+
+
+
+
+
+
+
+
+
+
+
+
+ このプロジェクトは、このコンピューター上にない NuGet パッケージを参照しています。それらのパッケージをダウンロードするには、[NuGet パッケージの復元] を使用します。詳細については、http://go.microsoft.com/fwlink/?LinkID=322105 を参照してください。見つからないファイルは {0} です。
+
+
+
+
+
diff --git a/TIASshot.Tests/packages.config b/TIASshot.Tests/packages.config
new file mode 100644
index 0000000..4648879
--- /dev/null
+++ b/TIASshot.Tests/packages.config
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/TIASshot.sln b/TIASshot.sln
index f86d34a..eec39e8 100644
--- a/TIASshot.sln
+++ b/TIASshot.sln
@@ -5,6 +5,8 @@
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TIASshot", "TIASshot\TIASshot.csproj", "{1D3B69FE-D6B4-42B9-BBCC-B8CB9CF3DE4F}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TIASshot.Tests", "TIASshot.Tests\TIASshot.Tests.csproj", "{6EEFE514-C4D9-4F71-9A8A-06F9748361CE}"
+EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ソリューション項目", "ソリューション項目", "{9A2B82DD-A13E-4B50-80D7-4983C769C3FC}"
ProjectSection(SolutionItems) = preProject
README.md = README.md
@@ -20,6 +22,10 @@
{1D3B69FE-D6B4-42B9-BBCC-B8CB9CF3DE4F}.Debug|x64.Build.0 = Debug|x64
{1D3B69FE-D6B4-42B9-BBCC-B8CB9CF3DE4F}.Release|x64.ActiveCfg = Release|x64
{1D3B69FE-D6B4-42B9-BBCC-B8CB9CF3DE4F}.Release|x64.Build.0 = Release|x64
+ {6EEFE514-C4D9-4F71-9A8A-06F9748361CE}.Debug|x64.ActiveCfg = Debug|x64
+ {6EEFE514-C4D9-4F71-9A8A-06F9748361CE}.Debug|x64.Build.0 = Debug|x64
+ {6EEFE514-C4D9-4F71-9A8A-06F9748361CE}.Release|x64.ActiveCfg = Release|x64
+ {6EEFE514-C4D9-4F71-9A8A-06F9748361CE}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/TIASshot/ColorCorrection/ColorCorrector.cs b/TIASshot/ColorCorrection/ColorCorrector.cs
index 2a31d51..1f12f72 100644
--- a/TIASshot/ColorCorrection/ColorCorrector.cs
+++ b/TIASshot/ColorCorrection/ColorCorrector.cs
@@ -45,20 +45,31 @@
};
///
+ /// 色補正クラス(テスト用:チャネル配列を直接受け取る)
+ ///
+ /// sRGB 変換の参照値(24x3)
+ /// XYZ 変換の参照値(24x3)
+ /// 処理対象のチャネル数一覧
+ internal ColorCorrector(Mat tccSrgb, Mat tccXyz, int[] channelList) {
+ TCC_SRGB = tccSrgb;
+ TCC_XYZ = tccXyz;
+ ChannelList = channelList;
+ }
+
+ ///
/// 色補正クラス
///
/// sRGB 変換の参照値(24x3)
/// XYZ 変換の参照値(24x3)
- public ColorCorrector(Mat tccSrgb, Mat tccXyz) {
- TCC_SRGB = tccSrgb;
- TCC_XYZ = tccXyz;
- ChannelList = Config.GetString("Calib/ConversionChannels")
- .Split(',')
- .Select(x => int.Parse(x.Trim()))
- .Where(x => x >= 4 && x <= 17)
- .Distinct()
- .OrderBy(x => x)
- .ToArray();
+ public ColorCorrector(Mat tccSrgb, Mat tccXyz)
+ : this(tccSrgb, tccXyz,
+ Config.GetString("Calib/ConversionChannels")
+ .Split(',')
+ .Select(x => int.Parse(x.Trim()))
+ .Where(x => x >= 4 && x <= 17)
+ .Distinct()
+ .OrderBy(x => x)
+ .ToArray()) {
}
/// sRGB 参照値が読み込めているか
@@ -139,7 +150,7 @@
///
/// 画像
/// 変換目標の24x3行列
- private Mat CalcConvertMatrix(Mat src, Mat target, int channels) {
+ internal Mat CalcConvertMatrix(Mat src, Mat target, int channels) {
var extended = ExtendMat(src, channels);
Mat convMat = new Mat(channels, 3, MatType.CV_64FC1);
Cv2.Solve(extended, target, convMat, DecompTypes.SVD);
@@ -152,7 +163,7 @@
///
///
///
- private Mat ConvertColor(Mat src, Mat conv) {
+ internal Mat ConvertColor(Mat src, Mat conv) {
var extended = ExtendMat(src, conv.Rows);
var converted = (extended * conv);
return converted.ToMat();
@@ -198,7 +209,7 @@
///
///
///
- private Mat ExtendMat(Mat src, int channels) {
+ internal Mat ExtendMat(Mat src, int channels) {
if (src.Cols * src.Channels() != 3) return src;
var dst = new Mat(src.Rows, channels, MatType.CV_64FC1);
Parallel.For(0, src.Rows, row => {
diff --git a/TIASshot/Config.cs b/TIASshot/Config.cs
index 9dc0629..9431682 100644
--- a/TIASshot/Config.cs
+++ b/TIASshot/Config.cs
@@ -19,11 +19,19 @@
public static void Load() {
try {
doc.Load(configFile);
- } catch (Exception) {
+ } catch (Exception) {
}
}
///
+ /// 指定したパスの設定ファイルを読み込む(テスト用)
+ ///
+ /// 読み込む XML ファイルのパス
+ internal static void Load(string path) {
+ doc.Load(path);
+ }
+
+ ///
/// 設定ファイル読み込み判定
///
///
diff --git a/TIASshot/Properties/AssemblyInfo.cs b/TIASshot/Properties/AssemblyInfo.cs
index 5dce7a9..308d4a7 100644
--- a/TIASshot/Properties/AssemblyInfo.cs
+++ b/TIASshot/Properties/AssemblyInfo.cs
@@ -1,6 +1,7 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
+[assembly: InternalsVisibleTo("TIASshot.Tests")]
// アセンブリに関する一般的な情報は、次の方法で制御されます
// 制御されます。アセンブリに関連付けられている情報を変更するには、
diff --git "a/docs/01_GUIDE/GUIDE_08_\343\203\206\343\202\271\343\203\210\346\226\271\351\207\235.md" "b/docs/01_GUIDE/GUIDE_08_\343\203\206\343\202\271\343\203\210\346\226\271\351\207\235.md"
index 07fdf35..60adc4f 100644
--- "a/docs/01_GUIDE/GUIDE_08_\343\203\206\343\202\271\343\203\210\346\226\271\351\207\235.md"
+++ "b/docs/01_GUIDE/GUIDE_08_\343\203\206\343\202\271\343\203\210\346\226\271\351\207\235.md"
@@ -71,6 +71,17 @@
- **粒度**: 1 テストにつき 1 つの検証観点とする.1 メソッドで多数の `Assert` を詰め込まない.
- **テストデータ**: 入力画像・基準 CSV 等は `TIASshot.Tests` 配下の専用フォルダ(例: `TestData/`)に置き,テストから相対参照する.
+## テストの実行 (Running Tests)
+
+テストプロジェクトは本体に合わせたレガシー形式(packages.config)のため `dotnet test` は使えない.ビルド後に `vstest.console.exe` を **x64** で実行する(ネイティブ DLL を x64 でロードするため).
+
+```powershell
+& "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\MSBuild\Current\Bin\MSBuild.exe" TIASshot.sln /t:Build /p:Configuration=Debug /p:Platform=x64
+& "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\Common7\IDE\CommonExtensions\Microsoft\TestWindow\vstest.console.exe" "TIASshot.Tests\bin\x64\Debug\TIASshot.Tests.dll" /Platform:x64
+```
+
+Visual Studio 上ではテストエクスプローラーから実行できる(プラットフォームを x64 にすること).
+
## モック方針 (Mocking)
- **過剰なモックを避ける**.ピュアロジックは実値(実際の Mat・数値)で検証する.
diff --git "a/docs/02_ENV/ENV_02_\343\203\207\343\202\243\343\203\254\343\202\257\343\203\210\343\203\252\346\247\213\346\210\220.md" "b/docs/02_ENV/ENV_02_\343\203\207\343\202\243\343\203\254\343\202\257\343\203\210\343\203\252\346\247\213\346\210\220.md"
index 77d975a..571cefc 100644
--- "a/docs/02_ENV/ENV_02_\343\203\207\343\202\243\343\203\254\343\202\257\343\203\210\343\203\252\346\247\213\346\210\220.md"
+++ "b/docs/02_ENV/ENV_02_\343\203\207\343\202\243\343\203\254\343\202\257\343\203\210\343\203\252\346\247\213\346\210\220.md"
@@ -8,9 +8,12 @@
├── README.md ← プロジェクト概要
├── TIASshot.sln ← Visual Studio ソリューションファイル
├── TIASshot/ ← メインプロジェクト
+├── TIASshot.Tests/ ← テストプロジェクト(NUnit / x64)
└── docs/ ← ドキュメント
```
+※ メインプロジェクト `TIASshot/` 配下のソース一覧(下記)はリファクタリング前の旧構成のままドリフトしている(現状は `Cameras/`・`UI/`・`ChartDetection/`・`ColorCorrection/`・`Utils/` 等に分離済み).別途修正が必要.
+
## メインプロジェクト TIASshot/ (Main Project)
### ソースコード
@@ -61,6 +64,17 @@
└── Settings.Designer.cs ← アプリ設定自動生成コード
```
+## テストプロジェクト TIASshot.Tests/ (Test Project)
+
+```text
+TIASshot.Tests/
+├── TIASshot.Tests.csproj ← テストプロジェクトファイル(net48 / x64)
+├── packages.config ← NuGet パッケージ一覧(NUnit 等)
+├── IoUtilTests.cs ← CSV 読み書き往復のテスト
+├── ConfigTests.cs ← config.xml パースのテスト
+└── ColorCorrectorTests.cs ← 次元拡張・変換行列・色変換のテスト
+```
+
## ドキュメント docs/ (Documents)
```text
diff --git "a/docs/02_ENV/ENV_03_\344\276\235\345\255\230\343\203\251\343\202\244\343\203\226\343\203\251\343\203\252\344\270\200\350\246\247.md" "b/docs/02_ENV/ENV_03_\344\276\235\345\255\230\343\203\251\343\202\244\343\203\226\343\203\251\343\203\252\344\270\200\350\246\247.md"
index 3ef2eac..5768bc2 100644
--- "a/docs/02_ENV/ENV_03_\344\276\235\345\255\230\343\203\251\343\202\244\343\203\226\343\203\251\343\203\252\344\270\200\350\246\247.md"
+++ "b/docs/02_ENV/ENV_03_\344\276\235\345\255\230\343\203\251\343\202\244\343\203\226\343\203\251\343\203\252\344\270\200\350\246\247.md"
@@ -26,6 +26,15 @@
- `System.Runtime.CompilerServices.Unsafe` (6.0.0)
- `System.ValueTuple` (4.5.0)
+## テストプロジェクト TIASshot.Tests (Test Project)
+
+テストプロジェクトも `packages.config` で管理する(本体と同じレガシー形式・x64).
+
+- `NUnit` (3.14.0): テストフレームワーク
+- `NUnit3TestAdapter` (4.5.0): テストランナー連携(Visual Studio / vstest)
+- `Microsoft.NET.Test.Sdk` (17.8.0): テスト実行基盤
+- ※ `OpenCvSharp4` / `OpenCvSharp4.runtime.win` (4.10.0) も参照する.`Mat` 演算はネイティブ DLL に依存するため,テストプロジェクトも **x64** でビルドする.
+
## 外部 SDK(別途インストールが必要) (External SDKs)
**IC Imaging Control SDK (The Imaging Source)**
diff --git a/docs/PROGRESS.md b/docs/PROGRESS.md
index 476fd0f..48f8b0c 100644
--- a/docs/PROGRESS.md
+++ b/docs/PROGRESS.md
@@ -36,3 +36,9 @@
- 動機: PLAN_02 優先度3(撮影中の例外をクラッシュさせずエラー表示・中断する修正)の前に方針を確定し,場当たり実装を避けるため。
- 設計判断: ログは `Debug.WriteLine` のみとし永続ログファイルは作らない(撮影現場での解析が必要になったら別途見直す)。空 `catch` 禁止・例外時のスレッド同期資源の `finally` 終端を明文化。
- 残: GUIDE_06 コーディング規約・GUIDE_07 ディレクトリ構造規則は未整備のまま(既存の de-facto 確立により優先度低)。
+
+- [x] **テストプロジェクト立ち上げ(TIASshot.Tests, NUnit)** (2026-06-07): GUIDE_08 に従い,ピュアロジック層を自動テストする基盤を構築。`ColorCorrector`(次元拡張・変換行列・色変換)・`Config`(パース)・`IoUtil`(CSV 往復)に初期テストを追加。
+ - 設計判断: 本体が packages.config 方式のレガシー形式のため,テストも同形式 x64 で統一(`OpenCvSharp4.runtime.win` のネイティブコピー規則をそのまま機能させ,`dotnet test` ではなく `vstest.console.exe /Platform:x64` で実行)。NUnit 3.14 を採用。
+ - テスト容易化のシーム(挙動不変): 本体に `[InternalsVisibleTo("TIASshot.Tests")]` 追加,`ColorCorrector` の純粋メソッドを private→internal・チャネル配列を受け取る internal コンストラクタ追加,`Config.Load(string path)` オーバーロード追加。
+ - ハマり所: テストでも OpenCvSharp ネイティブ DLL の **x64 ロード**が必須(`IoUtil` の CSV テストが通る=ネイティブ動作の試金石)。`.gitignore` を汎用 `bin/`・`obj/` に変更し,テストプロジェクトの成果物も除外。
+ - 後段: `GetRatio`(ゲイン補正式)は `CameraBase`(ハードウェア結合層)にインラインのため未テスト。抽出すればテスト候補。ENV_02 のメインソース一覧がリファクタ前構成のままドリフトしており,別途修正が必要。