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 のメインソース一覧がリファクタ前構成のままドリフトしており,別途修正が必要。