#0040 增加测试用例

This commit is contained in:
zhengxuan.zhang
2026-03-18 20:41:05 +08:00
parent 67898edc3f
commit 180501808e
8 changed files with 121 additions and 1621 deletions
@@ -2,23 +2,21 @@ using System;
using System.Collections.Generic;
using System.Linq;
using FsCheck;
using FsCheck.Fluent;
using XplorePlane.Models;
namespace XplorePlane.Tests.Generators
{
/// <summary>
/// FsCheck 自定义生成器:为配方相关模型定义 Arbitrary 生成器。
/// 使用 Arb.Register&lt;RecipeGenerators&gt;() 注册。
/// FsCheck 3.x 自定义生成器:为配方相关模型定义 Arbitrary 生成器。
/// </summary>
public class RecipeGenerators
{
// ── PipelineModel: class with settable properties ──
public static Arbitrary<PipelineModel> PipelineModelArb()
{
var gen = from id in Arb.Default.Guid().Generator
from name in Arb.Default.NonEmptyString().Generator
from deviceId in Arb.Default.NonEmptyString().Generator
var gen = from id in ArbMap.Default.GeneratorFor<Guid>()
from name in ArbMap.Default.GeneratorFor<NonEmptyString>()
from deviceId in ArbMap.Default.GeneratorFor<NonEmptyString>()
from ticks in Gen.Choose(0, int.MaxValue)
let createdAt = DateTime.MinValue.AddTicks((long)ticks * 10000)
let updatedAt = createdAt
@@ -31,11 +29,9 @@ namespace XplorePlane.Tests.Generators
UpdatedAt = updatedAt,
Nodes = new List<PipelineNodeModel>()
};
return Arb.From(gen);
return gen.ToArbitrary();
}
// ── RecipeStep: reuses StateGenerators for hardware states ──
public static Arbitrary<RecipeStep> RecipeStepArb()
{
var gen = from stepIndex in Gen.Choose(0, 100)
@@ -44,15 +40,13 @@ namespace XplorePlane.Tests.Generators
from detector in StateGenerators.DetectorStateArb().Generator
from pipeline in PipelineModelArb().Generator
select new RecipeStep(stepIndex, motion, ray, detector, pipeline);
return Arb.From(gen);
return gen.ToArbitrary();
}
// ── InspectionRecipe: 0-10 steps with sequential StepIndex ──
public static Arbitrary<InspectionRecipe> InspectionRecipeArb()
{
var gen = from id in Arb.Default.Guid().Generator
from name in Arb.Default.NonEmptyString().Generator
var gen = from id in ArbMap.Default.GeneratorFor<Guid>()
from name in ArbMap.Default.GeneratorFor<NonEmptyString>()
from ticks in Gen.Choose(0, int.MaxValue)
let createdAt = DateTime.MinValue.AddTicks((long)ticks * 10000)
let updatedAt = createdAt
@@ -64,11 +58,9 @@ namespace XplorePlane.Tests.Generators
createdAt,
updatedAt,
steps.AsReadOnly());
return Arb.From(gen);
return gen.ToArbitrary();
}
// ── RecipeExecutionState ──
public static Arbitrary<RecipeExecutionState> RecipeExecutionStateArb()
{
var gen = from totalSteps in Gen.Choose(0, 50)
@@ -79,27 +71,32 @@ namespace XplorePlane.Tests.Generators
RecipeExecutionStatus.Paused,
RecipeExecutionStatus.Completed,
RecipeExecutionStatus.Error)
from recipeName in Arb.Default.NonEmptyString().Generator
from recipeName in ArbMap.Default.GeneratorFor<NonEmptyString>()
select new RecipeExecutionState(currentStep, totalSteps, status, recipeName.Get);
return Arb.From(gen);
return gen.ToArbitrary();
}
// ── Helper: generate a list of steps with sequential StepIndex 0..n-1 ──
private static Gen<List<RecipeStep>> GenSequentialSteps(int count)
{
if (count == 0)
return Gen.Constant(new List<RecipeStep>());
return Gen.Sequence(
Enumerable.Range(0, count).Select(i =>
// Build list step by step using SelectMany chain
Gen<List<RecipeStep>> result = Gen.Constant(new List<RecipeStep>());
for (int i = 0; i < count; i++)
{
var idx = i;
result = from list in result
from motion in StateGenerators.MotionStateArb().Generator
from ray in StateGenerators.RaySourceStateArb().Generator
from detector in StateGenerators.DetectorStateArb().Generator
from pipeline in PipelineModelArb().Generator
select new RecipeStep(i, motion, ray, detector, pipeline)
)
).Select(steps => steps.ToList());
select new List<RecipeStep>(list)
{
new RecipeStep(idx, motion, ray, detector, pipeline)
};
}
return result;
}
}
}
+18 -36
View File
@@ -1,17 +1,15 @@
using System;
using FsCheck;
using FsCheck.Fluent;
using XplorePlane.Models;
namespace XplorePlane.Tests.Generators
{
/// <summary>
/// FsCheck 自定义生成器:为所有状态模型定义 Arbitrary 生成器。
/// 使用 Arb.Register&lt;StateGenerators&gt;() 注册。
/// FsCheck 3.x 自定义生成器:为所有状态模型定义 Arbitrary 生成器。
/// </summary>
public class StateGenerators
{
// ── 位置范围: -10000 ~ 10000, 速度范围: 0 ~ 1000 ──
private static Gen<double> PositionGen =>
Gen.Choose(-10000000, 10000000).Select(i => i / 1000.0);
@@ -30,8 +28,6 @@ namespace XplorePlane.Tests.Generators
private static Gen<double> MatrixGen =>
Gen.Choose(-10000000, 10000000).Select(i => i / 1000.0);
// ── MotionState: 6 positions + 6 speeds ──
public static Arbitrary<MotionState> MotionStateArb()
{
var gen = from xm in PositionGen
@@ -48,34 +44,28 @@ namespace XplorePlane.Tests.Generators
from distSpd in SpeedGen
select new MotionState(xm, ym, zt, zd, tiltD, dist,
xmSpd, ymSpd, ztSpd, zdSpd, tiltDSpd, distSpd);
return Arb.From(gen);
return gen.ToArbitrary();
}
// ── RaySourceState: bool + 2 doubles ──
public static Arbitrary<RaySourceState> RaySourceStateArb()
{
var gen = from isOn in Arb.Default.Bool().Generator
var gen = from isOn in ArbMap.Default.GeneratorFor<bool>()
from voltage in VoltageGen
from power in PowerGen
select new RaySourceState(isOn, voltage, power);
return Arb.From(gen);
return gen.ToArbitrary();
}
// ── DetectorState: 2 bools + double + string ──
public static Arbitrary<DetectorState> DetectorStateArb()
{
var gen = from isConnected in Arb.Default.Bool().Generator
from isAcquiring in Arb.Default.Bool().Generator
var gen = from isConnected in ArbMap.Default.GeneratorFor<bool>()
from isAcquiring in ArbMap.Default.GeneratorFor<bool>()
from frameRate in FrameRateGen
from res in Gen.Elements("1024x1024", "2048x2048", "2880x2880", "3072x3072", "4260x4260")
select new DetectorState(isConnected, isAcquiring, frameRate, res);
return Arb.From(gen);
return gen.ToArbitrary();
}
// ── SystemState: OperationMode enum + bool + string ──
public static Arbitrary<SystemState> SystemStateArb()
{
var gen = from mode in Gen.Elements(
@@ -83,28 +73,24 @@ namespace XplorePlane.Tests.Generators
OperationMode.Scanning,
OperationMode.CTAcquire,
OperationMode.RecipeRun)
from hasError in Arb.Default.Bool().Generator
from msg in Arb.Default.NonEmptyString().Generator
from hasError in ArbMap.Default.GeneratorFor<bool>()
from msg in ArbMap.Default.GeneratorFor<NonEmptyString>()
let errorMessage = hasError ? msg.Get : string.Empty
select new SystemState(mode, hasError, errorMessage);
return Arb.From(gen);
return gen.ToArbitrary();
}
// ── CameraState: 2 bools + null CurrentFrame + 2 ints + double ──
public static Arbitrary<CameraState> CameraStateArb()
{
var gen = from isConnected in Arb.Default.Bool().Generator
from isStreaming in Arb.Default.Bool().Generator
var gen = from isConnected in ArbMap.Default.GeneratorFor<bool>()
from isStreaming in ArbMap.Default.GeneratorFor<bool>()
from width in Gen.Choose(0, 8192)
from height in Gen.Choose(0, 8192)
from frameRate in FrameRateGen
select new CameraState(isConnected, isStreaming, null, width, height, frameRate);
return Arb.From(gen);
return gen.ToArbitrary();
}
// ── CalibrationMatrix: 9 doubles ──
public static Arbitrary<CalibrationMatrix> CalibrationMatrixArb()
{
var gen = from m11 in MatrixGen
@@ -117,30 +103,26 @@ namespace XplorePlane.Tests.Generators
from m32 in MatrixGen
from m33 in MatrixGen
select new CalibrationMatrix(m11, m12, m13, m21, m22, m23, m31, m32, m33);
return Arb.From(gen);
return gen.ToArbitrary();
}
// ── PhysicalPosition: 3 doubles ──
public static Arbitrary<PhysicalPosition> PhysicalPositionArb()
{
var gen = from x in PositionGen
from y in PositionGen
from z in PositionGen
select new PhysicalPosition(x, y, z);
return Arb.From(gen);
return gen.ToArbitrary();
}
// ── LinkedViewState: PhysicalPosition + bool + DateTime ──
public static Arbitrary<LinkedViewState> LinkedViewStateArb()
{
var gen = from pos in PhysicalPositionArb().Generator
from isExecuting in Arb.Default.Bool().Generator
from isExecuting in ArbMap.Default.GeneratorFor<bool>()
from ticks in Gen.Choose(0, int.MaxValue)
let dt = DateTime.MinValue.AddTicks((long)ticks * 10000)
select new LinkedViewState(pos, isExecuting, dt);
return Arb.From(gen);
return gen.ToArbitrary();
}
}
}
File diff suppressed because it is too large Load Diff
+19 -1
View File
@@ -1,17 +1,27 @@
using System;
using Xunit;
using Xunit.Abstractions;
using XplorePlane.Models;
namespace XplorePlane.Tests.Models
{
public class StateModelsTests
{
private readonly ITestOutputHelper _output;
public StateModelsTests(ITestOutputHelper output)
{
_output = output;
}
// ── Default Value Tests ───────────────────────────────────────
[Fact]
public void MotionState_Default_AllZeros()
{
var state = MotionState.Default;
_output.WriteLine($"MotionState.Default: XM={state.XM}, YM={state.YM}, ZT={state.ZT}, ZD={state.ZD}, TiltD={state.TiltD}, Dist={state.Dist}");
_output.WriteLine($" Speeds: XM={state.XMSpeed}, YM={state.YMSpeed}, ZT={state.ZTSpeed}, ZD={state.ZDSpeed}, TiltD={state.TiltDSpeed}, Dist={state.DistSpeed}");
Assert.Equal(0, state.XM);
Assert.Equal(0, state.YM);
@@ -31,6 +41,7 @@ namespace XplorePlane.Tests.Models
public void RaySourceState_Default_IsOffAndZeros()
{
var state = RaySourceState.Default;
_output.WriteLine($"RaySourceState.Default: IsOn={state.IsOn}, Voltage={state.Voltage}, Power={state.Power}");
Assert.False(state.IsOn);
Assert.Equal(0, state.Voltage);
@@ -41,6 +52,7 @@ namespace XplorePlane.Tests.Models
public void DetectorState_Default_DisconnectedAndZeros()
{
var state = DetectorState.Default;
_output.WriteLine($"DetectorState.Default: IsConnected={state.IsConnected}, IsAcquiring={state.IsAcquiring}, FrameRate={state.FrameRate}, Resolution='{state.Resolution}'");
Assert.False(state.IsConnected);
Assert.False(state.IsAcquiring);
@@ -52,6 +64,7 @@ namespace XplorePlane.Tests.Models
public void SystemState_Default_IdleNoError()
{
var state = SystemState.Default;
_output.WriteLine($"SystemState.Default: OperationMode={state.OperationMode}, HasError={state.HasError}, ErrorMessage='{state.ErrorMessage}'");
Assert.Equal(OperationMode.Idle, state.OperationMode);
Assert.False(state.HasError);
@@ -62,6 +75,7 @@ namespace XplorePlane.Tests.Models
public void CameraState_Default_DisconnectedAndZeros()
{
var state = CameraState.Default;
_output.WriteLine($"CameraState.Default: IsConnected={state.IsConnected}, IsStreaming={state.IsStreaming}, CurrentFrame={state.CurrentFrame}, Width={state.Width}, Height={state.Height}, FrameRate={state.FrameRate}");
Assert.False(state.IsConnected);
Assert.False(state.IsStreaming);
@@ -75,6 +89,7 @@ namespace XplorePlane.Tests.Models
public void LinkedViewState_Default_ZeroPositionNotExecuting()
{
var state = LinkedViewState.Default;
_output.WriteLine($"LinkedViewState.Default: TargetPosition=({state.TargetPosition.X}, {state.TargetPosition.Y}, {state.TargetPosition.Z}), IsExecuting={state.IsExecuting}, LastRequestTime={state.LastRequestTime}");
Assert.Equal(0, state.TargetPosition.X);
Assert.Equal(0, state.TargetPosition.Y);
@@ -87,6 +102,7 @@ namespace XplorePlane.Tests.Models
public void RecipeExecutionState_Default_IdleAndZeros()
{
var state = RecipeExecutionState.Default;
_output.WriteLine($"RecipeExecutionState.Default: CurrentStepIndex={state.CurrentStepIndex}, TotalSteps={state.TotalSteps}, Status={state.Status}, CurrentRecipeName='{state.CurrentRecipeName}'");
Assert.Equal(0, state.CurrentStepIndex);
Assert.Equal(0, state.TotalSteps);
@@ -100,8 +116,8 @@ namespace XplorePlane.Tests.Models
public void MotionState_WithExpression_ProducesNewInstance()
{
var original = MotionState.Default;
var modified = original with { XM = 100 };
_output.WriteLine($"Original.XM={original.XM}, Modified.XM={modified.XM}, SameRef={ReferenceEquals(original, modified)}");
// New instance is different from original
Assert.NotSame(original, modified);
@@ -124,6 +140,7 @@ namespace XplorePlane.Tests.Models
);
var (x, y, z) = matrix.Transform(pixelX: 5, pixelY: 8);
_output.WriteLine($"Matrix Transform(5, 8): x={x}, y={y}, z={z} (expected: 20, 44, 0)");
// x = 2*5 + 0*8 + 10 = 20
Assert.Equal(20, x);
@@ -144,6 +161,7 @@ namespace XplorePlane.Tests.Models
);
var (x, y, z) = identity.Transform(pixelX: 42.5, pixelY: 99.1);
_output.WriteLine($"Identity Transform(42.5, 99.1): x={x}, y={y}, z={z}");
Assert.Equal(42.5, x, precision: 10);
Assert.Equal(99.1, y, precision: 10);
@@ -3,6 +3,7 @@ using System.Windows;
using Moq;
using Serilog;
using Xunit;
using Xunit.Abstractions;
using XP.Hardware.RaySource.Services;
using XplorePlane.Models;
using XplorePlane.Services.AppState;
@@ -18,9 +19,12 @@ namespace XplorePlane.Tests.Services
private readonly AppStateService _service;
private readonly Mock<IRaySourceService> _mockRaySource;
private readonly Mock<ILogger> _mockLogger;
private readonly ITestOutputHelper _output;
public AppStateServiceTests()
public AppStateServiceTests(ITestOutputHelper output)
{
_output = output;
// Ensure WPF Application exists for Dispatcher
if (Application.Current == null)
{
@@ -42,24 +46,28 @@ namespace XplorePlane.Tests.Services
[Fact]
public void DefaultState_MotionState_IsDefault()
{
_output.WriteLine($"MotionState == MotionState.Default: {ReferenceEquals(MotionState.Default, _service.MotionState)}");
Assert.Same(MotionState.Default, _service.MotionState);
}
[Fact]
public void DefaultState_RaySourceState_IsDefault()
{
_output.WriteLine($"RaySourceState == RaySourceState.Default: {ReferenceEquals(RaySourceState.Default, _service.RaySourceState)}");
Assert.Same(RaySourceState.Default, _service.RaySourceState);
}
[Fact]
public void DefaultState_SystemState_IsDefault()
{
_output.WriteLine($"SystemState == SystemState.Default: {ReferenceEquals(SystemState.Default, _service.SystemState)}");
Assert.Same(SystemState.Default, _service.SystemState);
}
[Fact]
public void DefaultState_CalibrationMatrix_IsNull()
{
_output.WriteLine($"CalibrationMatrix: {_service.CalibrationMatrix?.ToString() ?? "null"}");
Assert.Null(_service.CalibrationMatrix);
}
@@ -68,25 +76,29 @@ namespace XplorePlane.Tests.Services
[Fact]
public void UpdateMotionState_NullArgument_ThrowsArgumentNullException()
{
Assert.Throws<ArgumentNullException>(() => _service.UpdateMotionState(null!));
var ex = Assert.Throws<ArgumentNullException>(() => _service.UpdateMotionState(null!));
_output.WriteLine($"UpdateMotionState(null) threw: {ex.GetType().Name}, Param={ex.ParamName}");
}
[Fact]
public void UpdateRaySourceState_NullArgument_ThrowsArgumentNullException()
{
Assert.Throws<ArgumentNullException>(() => _service.UpdateRaySourceState(null!));
var ex = Assert.Throws<ArgumentNullException>(() => _service.UpdateRaySourceState(null!));
_output.WriteLine($"UpdateRaySourceState(null) threw: {ex.GetType().Name}, Param={ex.ParamName}");
}
[Fact]
public void UpdateDetectorState_NullArgument_ThrowsArgumentNullException()
{
Assert.Throws<ArgumentNullException>(() => _service.UpdateDetectorState(null!));
var ex = Assert.Throws<ArgumentNullException>(() => _service.UpdateDetectorState(null!));
_output.WriteLine($"UpdateDetectorState(null) threw: {ex.GetType().Name}, Param={ex.ParamName}");
}
[Fact]
public void UpdateSystemState_NullArgument_ThrowsArgumentNullException()
{
Assert.Throws<ArgumentNullException>(() => _service.UpdateSystemState(null!));
var ex = Assert.Throws<ArgumentNullException>(() => _service.UpdateSystemState(null!));
_output.WriteLine($"UpdateSystemState(null) threw: {ex.GetType().Name}, Param={ex.ParamName}");
}
// ── Dispose 后 Update 被忽略 ──
@@ -96,11 +108,13 @@ namespace XplorePlane.Tests.Services
{
var originalState = _service.MotionState;
_service.Dispose();
_output.WriteLine("Service disposed, attempting UpdateMotionState...");
// Should not throw, and state should remain unchanged
var newState = new MotionState(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12);
_service.UpdateMotionState(newState);
_output.WriteLine($"State unchanged after dispose: {ReferenceEquals(originalState, _service.MotionState)}");
Assert.Same(originalState, _service.MotionState);
}
@@ -114,6 +128,7 @@ namespace XplorePlane.Tests.Services
_service.RequestLinkedView(100.0, 200.0);
_output.WriteLine($"RequestLinkedView(100, 200) without CalibrationMatrix: HasError={_service.SystemState.HasError}, ErrorMessage='{_service.SystemState.ErrorMessage}'");
Assert.True(_service.SystemState.HasError);
Assert.NotEmpty(_service.SystemState.ErrorMessage);
}
@@ -4,6 +4,7 @@ using System.Threading.Tasks;
using Moq;
using Serilog;
using Xunit;
using Xunit.Abstractions;
using XplorePlane.Models;
using XplorePlane.Services;
using XplorePlane.Services.AppState;
@@ -21,9 +22,11 @@ namespace XplorePlane.Tests.Services
private readonly Mock<IPipelineExecutionService> _mockPipeline;
private readonly Mock<ILogger> _mockLogger;
private readonly RecipeService _service;
private readonly ITestOutputHelper _output;
public RecipeServiceTests()
public RecipeServiceTests(ITestOutputHelper output)
{
_output = output;
_mockAppState = new Mock<IAppStateService>();
_mockPipeline = new Mock<IPipelineExecutionService>();
_mockLogger = new Mock<ILogger>();
@@ -45,6 +48,7 @@ namespace XplorePlane.Tests.Services
public void CreateRecipe_ValidName_ReturnsEmptyRecipe()
{
var recipe = _service.CreateRecipe("TestRecipe");
_output.WriteLine($"CreateRecipe('TestRecipe'): Name={recipe.Name}, Id={recipe.Id}, Steps.Count={recipe.Steps.Count}, CreatedAt={recipe.CreatedAt}");
Assert.Equal("TestRecipe", recipe.Name);
Assert.Empty(recipe.Steps);
@@ -56,19 +60,22 @@ namespace XplorePlane.Tests.Services
[Fact]
public void CreateRecipe_EmptyName_ThrowsArgumentException()
{
Assert.Throws<ArgumentException>(() => _service.CreateRecipe(string.Empty));
var ex = Assert.Throws<ArgumentException>(() => _service.CreateRecipe(string.Empty));
_output.WriteLine($"CreateRecipe('') threw: {ex.GetType().Name}, Message={ex.Message}");
}
[Fact]
public void CreateRecipe_NullName_ThrowsArgumentException()
{
Assert.Throws<ArgumentException>(() => _service.CreateRecipe(null!));
var ex = Assert.Throws<ArgumentException>(() => _service.CreateRecipe(null!));
_output.WriteLine($"CreateRecipe(null) threw: {ex.GetType().Name}, Message={ex.Message}");
}
[Fact]
public void CreateRecipe_WhitespaceName_ThrowsArgumentException()
{
Assert.Throws<ArgumentException>(() => _service.CreateRecipe(" "));
var ex = Assert.Throws<ArgumentException>(() => _service.CreateRecipe(" "));
_output.WriteLine($"CreateRecipe(' ') threw: {ex.GetType().Name}, Message={ex.Message}");
}
// ── RecordCurrentStep 验证 ──
@@ -88,6 +95,7 @@ namespace XplorePlane.Tests.Services
var pipeline = new PipelineModel { Name = "TestPipeline" };
var step = _service.RecordCurrentStep(recipe, pipeline);
_output.WriteLine($"RecordCurrentStep: StepIndex={step.StepIndex}, MotionState.XM={step.MotionState.XM}, RaySource.Voltage={step.RaySourceState.Voltage}, Detector.Resolution={step.DetectorState.Resolution}, Pipeline={step.Pipeline.Name}");
Assert.Equal(0, step.StepIndex);
Assert.Same(motionState, step.MotionState);
@@ -102,9 +110,11 @@ namespace XplorePlane.Tests.Services
public async Task LoadAsync_FileNotExists_ThrowsFileNotFoundException()
{
var nonExistentPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString() + ".json");
_output.WriteLine($"LoadAsync 不存在的文件: {nonExistentPath}");
await Assert.ThrowsAsync<FileNotFoundException>(
var ex = await Assert.ThrowsAsync<FileNotFoundException>(
() => _service.LoadAsync(nonExistentPath));
_output.WriteLine($"抛出异常: {ex.GetType().Name}, Message={ex.Message}");
}
[Fact]
@@ -114,9 +124,11 @@ namespace XplorePlane.Tests.Services
try
{
await File.WriteAllTextAsync(tempFile, "{ this is not valid json !!! }");
_output.WriteLine($"LoadAsync 无效JSON文件: {tempFile}");
await Assert.ThrowsAsync<InvalidDataException>(
var ex = await Assert.ThrowsAsync<InvalidDataException>(
() => _service.LoadAsync(tempFile));
_output.WriteLine($"抛出异常: {ex.GetType().Name}, Message={ex.Message}");
}
finally
{
+14 -2
View File
@@ -3,16 +3,24 @@
<PropertyGroup>
<TargetFramework>net8.0-windows</TargetFramework>
<UseWPF>true</UseWPF>
<OutputType>Library</OutputType>
<IsPackable>false</IsPackable>
<RootNamespace>XplorePlane.Tests</RootNamespace>
</PropertyGroup>
<ItemGroup>
<Compile Remove="Helpers\Define.cs" />
</ItemGroup>
<ItemGroup>
<None Remove="Helpers\Define.cs.bak" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.*" />
<PackageReference Include="xunit" Version="2.*" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.*" />
<PackageReference Include="FsCheck" Version="2.*" />
<PackageReference Include="FsCheck.Xunit" Version="2.*" />
<PackageReference Include="FsCheck" Version="3.*" />
<PackageReference Include="FsCheck.Xunit" Version="3.*" />
<PackageReference Include="Moq" Version="4.*" />
</ItemGroup>
@@ -29,6 +37,10 @@
<HintPath>..\XplorePlane\Libs\Hardware\XP.Hardware.RaySource.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="ImageProcessing.Core">
<HintPath>..\XplorePlane\Libs\ImageProcessing\ImageProcessing.Core.dll</HintPath>
<Private>True</Private>
</Reference>
</ItemGroup>
</Project>
+2
View File
@@ -5,6 +5,7 @@
<UseWPF>true</UseWPF>
<RootNamespace>XplorePlane</RootNamespace>
<AssemblyName>XplorePlane</AssemblyName>
<ApplicationIcon>GapInspect.ico</ApplicationIcon>
</PropertyGroup>
<ItemGroup>
@@ -186,6 +187,7 @@
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<Link>Libs\ImageProcessing\zh-CN\%(Filename)%(Extension)</Link>
</None>
<Content Include="GapInspect.ico" />
<!-- 配置文件 -->
<None Update="App.config">