#0039 全局数据结构设计
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using Moq;
|
||||
using Serilog;
|
||||
using Xunit;
|
||||
using XP.Hardware.RaySource.Services;
|
||||
using XplorePlane.Models;
|
||||
using XplorePlane.Services.AppState;
|
||||
|
||||
namespace XplorePlane.Tests.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// AppStateService 单元测试。
|
||||
/// 验证默认状态值、Dispose 后行为、null 参数校验、CalibrationMatrix 缺失时的错误处理。
|
||||
/// </summary>
|
||||
public class AppStateServiceTests : IDisposable
|
||||
{
|
||||
private readonly AppStateService _service;
|
||||
private readonly Mock<IRaySourceService> _mockRaySource;
|
||||
private readonly Mock<ILogger> _mockLogger;
|
||||
|
||||
public AppStateServiceTests()
|
||||
{
|
||||
// Ensure WPF Application exists for Dispatcher
|
||||
if (Application.Current == null)
|
||||
{
|
||||
new Application();
|
||||
}
|
||||
|
||||
_mockRaySource = new Mock<IRaySourceService>();
|
||||
_mockLogger = new Mock<ILogger>();
|
||||
_service = new AppStateService(_mockRaySource.Object, _mockLogger.Object);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_service.Dispose();
|
||||
}
|
||||
|
||||
// ── 默认状态值验证 ──
|
||||
|
||||
[Fact]
|
||||
public void DefaultState_MotionState_IsDefault()
|
||||
{
|
||||
Assert.Same(MotionState.Default, _service.MotionState);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DefaultState_RaySourceState_IsDefault()
|
||||
{
|
||||
Assert.Same(RaySourceState.Default, _service.RaySourceState);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DefaultState_SystemState_IsDefault()
|
||||
{
|
||||
Assert.Same(SystemState.Default, _service.SystemState);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DefaultState_CalibrationMatrix_IsNull()
|
||||
{
|
||||
Assert.Null(_service.CalibrationMatrix);
|
||||
}
|
||||
|
||||
// ── null 参数抛出 ArgumentNullException ──
|
||||
|
||||
[Fact]
|
||||
public void UpdateMotionState_NullArgument_ThrowsArgumentNullException()
|
||||
{
|
||||
Assert.Throws<ArgumentNullException>(() => _service.UpdateMotionState(null!));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UpdateRaySourceState_NullArgument_ThrowsArgumentNullException()
|
||||
{
|
||||
Assert.Throws<ArgumentNullException>(() => _service.UpdateRaySourceState(null!));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UpdateDetectorState_NullArgument_ThrowsArgumentNullException()
|
||||
{
|
||||
Assert.Throws<ArgumentNullException>(() => _service.UpdateDetectorState(null!));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UpdateSystemState_NullArgument_ThrowsArgumentNullException()
|
||||
{
|
||||
Assert.Throws<ArgumentNullException>(() => _service.UpdateSystemState(null!));
|
||||
}
|
||||
|
||||
// ── Dispose 后 Update 被忽略 ──
|
||||
|
||||
[Fact]
|
||||
public void Dispose_ThenUpdate_IsIgnored()
|
||||
{
|
||||
var originalState = _service.MotionState;
|
||||
_service.Dispose();
|
||||
|
||||
// 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);
|
||||
|
||||
Assert.Same(originalState, _service.MotionState);
|
||||
}
|
||||
|
||||
// ── CalibrationMatrix 为 null 时 RequestLinkedView 设置错误状态 ──
|
||||
|
||||
[Fact]
|
||||
public void RequestLinkedView_NoCalibrationMatrix_SetsErrorState()
|
||||
{
|
||||
// CalibrationMatrix is null by default
|
||||
Assert.Null(_service.CalibrationMatrix);
|
||||
|
||||
_service.RequestLinkedView(100.0, 200.0);
|
||||
|
||||
Assert.True(_service.SystemState.HasError);
|
||||
Assert.NotEmpty(_service.SystemState.ErrorMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Moq;
|
||||
using Serilog;
|
||||
using Xunit;
|
||||
using XplorePlane.Models;
|
||||
using XplorePlane.Services;
|
||||
using XplorePlane.Services.AppState;
|
||||
using XplorePlane.Services.Recipe;
|
||||
|
||||
namespace XplorePlane.Tests.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// RecipeService 单元测试。
|
||||
/// 验证配方创建、步骤记录、文件加载错误处理和名称校验。
|
||||
/// </summary>
|
||||
public class RecipeServiceTests
|
||||
{
|
||||
private readonly Mock<IAppStateService> _mockAppState;
|
||||
private readonly Mock<IPipelineExecutionService> _mockPipeline;
|
||||
private readonly Mock<ILogger> _mockLogger;
|
||||
private readonly RecipeService _service;
|
||||
|
||||
public RecipeServiceTests()
|
||||
{
|
||||
_mockAppState = new Mock<IAppStateService>();
|
||||
_mockPipeline = new Mock<IPipelineExecutionService>();
|
||||
_mockLogger = new Mock<ILogger>();
|
||||
|
||||
// Setup default state returns
|
||||
_mockAppState.Setup(s => s.MotionState).Returns(MotionState.Default);
|
||||
_mockAppState.Setup(s => s.RaySourceState).Returns(RaySourceState.Default);
|
||||
_mockAppState.Setup(s => s.DetectorState).Returns(DetectorState.Default);
|
||||
|
||||
_service = new RecipeService(
|
||||
_mockAppState.Object,
|
||||
_mockPipeline.Object,
|
||||
_mockLogger.Object);
|
||||
}
|
||||
|
||||
// ── CreateRecipe 验证 ──
|
||||
|
||||
[Fact]
|
||||
public void CreateRecipe_ValidName_ReturnsEmptyRecipe()
|
||||
{
|
||||
var recipe = _service.CreateRecipe("TestRecipe");
|
||||
|
||||
Assert.Equal("TestRecipe", recipe.Name);
|
||||
Assert.Empty(recipe.Steps);
|
||||
Assert.NotEqual(Guid.Empty, recipe.Id);
|
||||
Assert.True(recipe.CreatedAt <= DateTime.UtcNow);
|
||||
Assert.True(recipe.UpdatedAt <= DateTime.UtcNow);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateRecipe_EmptyName_ThrowsArgumentException()
|
||||
{
|
||||
Assert.Throws<ArgumentException>(() => _service.CreateRecipe(string.Empty));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateRecipe_NullName_ThrowsArgumentException()
|
||||
{
|
||||
Assert.Throws<ArgumentException>(() => _service.CreateRecipe(null!));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateRecipe_WhitespaceName_ThrowsArgumentException()
|
||||
{
|
||||
Assert.Throws<ArgumentException>(() => _service.CreateRecipe(" "));
|
||||
}
|
||||
|
||||
// ── RecordCurrentStep 验证 ──
|
||||
|
||||
[Fact]
|
||||
public void RecordCurrentStep_CapturesCurrentState()
|
||||
{
|
||||
var motionState = new MotionState(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12);
|
||||
var raySourceState = new RaySourceState(true, 120.0, 80.0);
|
||||
var detectorState = new DetectorState(true, true, 30.0, "2048x2048");
|
||||
|
||||
_mockAppState.Setup(s => s.MotionState).Returns(motionState);
|
||||
_mockAppState.Setup(s => s.RaySourceState).Returns(raySourceState);
|
||||
_mockAppState.Setup(s => s.DetectorState).Returns(detectorState);
|
||||
|
||||
var recipe = _service.CreateRecipe("TestRecipe");
|
||||
var pipeline = new PipelineModel { Name = "TestPipeline" };
|
||||
|
||||
var step = _service.RecordCurrentStep(recipe, pipeline);
|
||||
|
||||
Assert.Equal(0, step.StepIndex);
|
||||
Assert.Same(motionState, step.MotionState);
|
||||
Assert.Same(raySourceState, step.RaySourceState);
|
||||
Assert.Same(detectorState, step.DetectorState);
|
||||
Assert.Same(pipeline, step.Pipeline);
|
||||
}
|
||||
|
||||
// ── LoadAsync 错误处理 ──
|
||||
|
||||
[Fact]
|
||||
public async Task LoadAsync_FileNotExists_ThrowsFileNotFoundException()
|
||||
{
|
||||
var nonExistentPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString() + ".json");
|
||||
|
||||
await Assert.ThrowsAsync<FileNotFoundException>(
|
||||
() => _service.LoadAsync(nonExistentPath));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task LoadAsync_InvalidJson_ThrowsInvalidDataException()
|
||||
{
|
||||
var tempFile = Path.GetTempFileName();
|
||||
try
|
||||
{
|
||||
await File.WriteAllTextAsync(tempFile, "{ this is not valid json !!! }");
|
||||
|
||||
await Assert.ThrowsAsync<InvalidDataException>(
|
||||
() => _service.LoadAsync(tempFile));
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (File.Exists(tempFile))
|
||||
File.Delete(tempFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user