434 lines
14 KiB
C#
434 lines
14 KiB
C#
using System;
|
||
using System.Threading;
|
||
using System.Threading.Tasks;
|
||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||
using NavisworksTransport.Commands;
|
||
|
||
namespace NavisworksTransport.UnitTests.Commands
|
||
{
|
||
/// <summary>
|
||
/// CommandBase抽象类的纯逻辑测试
|
||
/// 通过创建测试实现类来测试基础功能,不依赖Navisworks环境
|
||
/// </summary>
|
||
[TestClass]
|
||
public class CommandBaseTests
|
||
{
|
||
private TestCommand _testCommand;
|
||
|
||
[TestInitialize]
|
||
public void SetUp()
|
||
{
|
||
_testCommand = new TestCommand();
|
||
}
|
||
|
||
[TestCleanup]
|
||
public void TearDown()
|
||
{
|
||
_testCommand?.Cancel();
|
||
}
|
||
|
||
#region 构造函数测试
|
||
|
||
[TestMethod]
|
||
public void Constructor_Default_SetsCorrectDefaults()
|
||
{
|
||
// Arrange & Act
|
||
var command = new TestCommand();
|
||
|
||
// Assert
|
||
Assert.IsNotNull(command.CommandId, "CommandId不应该为空");
|
||
Assert.IsTrue(Guid.TryParse(command.CommandId, out _), "CommandId应该是有效的GUID");
|
||
Assert.AreEqual("TestCommand", command.DisplayName, "DisplayName应该默认为类名");
|
||
Assert.AreEqual("路径规划命令", command.Description, "Description应该有默认值");
|
||
Assert.AreEqual(CommandExecutionStatus.NotStarted, command.Status, "初始状态应该为NotStarted");
|
||
Assert.AreEqual(0, command.Progress, "初始进度应该为0");
|
||
}
|
||
|
||
[TestMethod]
|
||
public void Constructor_WithParameters_SetsCorrectValues()
|
||
{
|
||
// Arrange
|
||
string expectedCommandId = "test-command-123";
|
||
string expectedDisplayName = "测试命令";
|
||
string expectedDescription = "这是一个测试命令";
|
||
|
||
// Act
|
||
var command = new TestCommand(expectedCommandId, expectedDisplayName, expectedDescription);
|
||
|
||
// Assert
|
||
Assert.AreEqual(expectedCommandId, command.CommandId, "CommandId应该设置为指定值");
|
||
Assert.AreEqual(expectedDisplayName, command.DisplayName, "DisplayName应该设置为指定值");
|
||
Assert.AreEqual(expectedDescription, command.Description, "Description应该设置为指定值");
|
||
}
|
||
|
||
[TestMethod]
|
||
public void Constructor_WithNullParameters_SetsDefaults()
|
||
{
|
||
// Arrange & Act
|
||
var command = new TestCommand(null, null, null);
|
||
|
||
// Assert
|
||
Assert.IsNotNull(command.CommandId, "CommandId不应该为空");
|
||
Assert.IsTrue(Guid.TryParse(command.CommandId, out _), "CommandId应该是有效的GUID");
|
||
Assert.AreEqual("TestCommand", command.DisplayName, "DisplayName应该默认为类名");
|
||
Assert.AreEqual("路径规划命令", command.Description, "Description应该有默认值");
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 状态管理测试
|
||
|
||
[TestMethod]
|
||
public void Status_MultipleThreadsAccess_ThreadSafe()
|
||
{
|
||
// Arrange
|
||
var command = new TestCommand();
|
||
var exceptions = new List<Exception>();
|
||
var tasks = new List<Task>();
|
||
|
||
// Act - 多线程并发访问Status
|
||
for (int i = 0; i < 10; i++)
|
||
{
|
||
tasks.Add(Task.Run(() =>
|
||
{
|
||
try
|
||
{
|
||
for (int j = 0; j < 100; j++)
|
||
{
|
||
var status = command.Status; // 读取状态
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
lock (exceptions)
|
||
{
|
||
exceptions.Add(ex);
|
||
}
|
||
}
|
||
}));
|
||
}
|
||
|
||
Task.WaitAll(tasks.ToArray());
|
||
|
||
// Assert
|
||
Assert.AreEqual(0, exceptions.Count, "多线程访问Status不应该抛出异常");
|
||
}
|
||
|
||
[TestMethod]
|
||
public void Progress_MultipleThreadsAccess_ThreadSafe()
|
||
{
|
||
// Arrange
|
||
var command = new TestCommand();
|
||
var exceptions = new List<Exception>();
|
||
var tasks = new List<Task>();
|
||
|
||
// Act - 多线程并发访问Progress
|
||
for (int i = 0; i < 10; i++)
|
||
{
|
||
tasks.Add(Task.Run(() =>
|
||
{
|
||
try
|
||
{
|
||
for (int j = 0; j < 100; j++)
|
||
{
|
||
var progress = command.Progress; // 读取进度
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
lock (exceptions)
|
||
{
|
||
exceptions.Add(ex);
|
||
}
|
||
}
|
||
}));
|
||
}
|
||
|
||
Task.WaitAll(tasks.ToArray());
|
||
|
||
// Assert
|
||
Assert.AreEqual(0, exceptions.Count, "多线程访问Progress不应该抛出异常");
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 事件测试
|
||
|
||
[TestMethod]
|
||
public void StatusChanged_EventRaised_WhenStatusChanges()
|
||
{
|
||
// Arrange
|
||
var command = new TestCommand();
|
||
CommandStatusChangedEventArgs receivedEventArgs = null;
|
||
command.StatusChanged += (sender, args) => receivedEventArgs = args;
|
||
|
||
// Act
|
||
command.SimulateStatusChangeEvent(CommandExecutionStatus.NotStarted, CommandExecutionStatus.Executing);
|
||
|
||
// Assert
|
||
Assert.IsNotNull(receivedEventArgs, "StatusChanged事件应该被触发");
|
||
Assert.AreEqual(CommandExecutionStatus.NotStarted, receivedEventArgs.OldStatus, "旧状态应该正确");
|
||
Assert.AreEqual(CommandExecutionStatus.Executing, receivedEventArgs.NewStatus, "新状态应该正确");
|
||
}
|
||
|
||
[TestMethod]
|
||
public void ProgressChanged_EventRaised_WhenProgressChanges()
|
||
{
|
||
// Arrange
|
||
var command = new TestCommand();
|
||
CommandProgressChangedEventArgs receivedEventArgs = null;
|
||
command.ProgressChanged += (sender, args) => receivedEventArgs = args;
|
||
|
||
// Act
|
||
command.SimulateProgressChange(50, "进度50%");
|
||
|
||
// Assert
|
||
Assert.IsNotNull(receivedEventArgs, "ProgressChanged事件应该被触发");
|
||
Assert.AreEqual(50, receivedEventArgs.Progress, "进度值应该正确");
|
||
Assert.AreEqual("进度50%", receivedEventArgs.StatusMessage, "状态消息应该正确");
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 执行测试
|
||
|
||
[TestMethod]
|
||
public async Task ExecuteAsync_Success_ReturnsSuccessResult()
|
||
{
|
||
// Arrange
|
||
var command = new TestCommand();
|
||
command.SetShouldSucceed(true);
|
||
|
||
// Act
|
||
var result = await command.ExecuteAsync();
|
||
|
||
// Assert
|
||
Assert.IsTrue(result.IsSuccess, "执行应该成功");
|
||
Assert.AreEqual(CommandExecutionStatus.Completed, command.Status, "状态应该为Completed");
|
||
Assert.AreEqual(100, command.Progress, "进度应该为100");
|
||
}
|
||
|
||
[TestMethod]
|
||
public async Task ExecuteAsync_Failure_ReturnsFailureResult()
|
||
{
|
||
// Arrange
|
||
var command = new TestCommand();
|
||
command.SetShouldSucceed(false);
|
||
|
||
// Act
|
||
var result = await command.ExecuteAsync();
|
||
|
||
// Assert
|
||
Assert.IsFalse(result.IsSuccess, "执行应该失败");
|
||
Assert.AreEqual(CommandExecutionStatus.Failed, command.Status, "状态应该为Failed");
|
||
}
|
||
|
||
[TestMethod]
|
||
public async Task ExecuteAsync_ValidationFailure_ReturnsValidationFailure()
|
||
{
|
||
// Arrange
|
||
var command = new TestCommand();
|
||
command.SetValidationShouldFail(true);
|
||
|
||
// Act
|
||
var result = await command.ExecuteAsync();
|
||
|
||
// Assert
|
||
Assert.IsFalse(result.IsSuccess, "执行应该失败");
|
||
Assert.AreEqual(CommandExecutionStatus.Failed, command.Status, "状态应该为Failed");
|
||
Assert.IsTrue(result.ErrorMessage.Contains("验证失败"), "错误消息应该包含验证失败信息");
|
||
}
|
||
|
||
[TestMethod]
|
||
public async Task ExecuteAsync_CancellationRequested_ReturnsCancelledResult()
|
||
{
|
||
// Arrange
|
||
var command = new TestCommand();
|
||
command.SetExecutionDelay(TimeSpan.FromSeconds(2)); // 设置较长的执行时间
|
||
var cts = new CancellationTokenSource();
|
||
|
||
// Act
|
||
var executeTask = command.ExecuteAsync(cts.Token);
|
||
cts.CancelAfter(100); // 100ms后取消
|
||
var result = await executeTask;
|
||
|
||
// Assert
|
||
Assert.IsFalse(result.IsSuccess, "执行应该失败");
|
||
Assert.AreEqual(CommandExecutionStatus.Cancelled, command.Status, "状态应该为Cancelled");
|
||
Assert.IsTrue(result.ErrorMessage.Contains("取消"), "错误消息应该包含取消信息");
|
||
}
|
||
|
||
[TestMethod]
|
||
public async Task ExecuteAsync_AlreadyExecuting_ReturnsFailure()
|
||
{
|
||
// Arrange
|
||
var command = new TestCommand();
|
||
command.SetExecutionDelay(TimeSpan.FromSeconds(1));
|
||
|
||
// Act
|
||
var task1 = command.ExecuteAsync();
|
||
await Task.Delay(50); // 确保第一个任务开始执行
|
||
var result2 = await command.ExecuteAsync(); // 尝试重复执行
|
||
|
||
// Assert
|
||
Assert.IsFalse(result2.IsSuccess, "重复执行应该失败");
|
||
Assert.IsTrue(result2.ErrorMessage.Contains("正在执行中"), "错误消息应该指示命令正在执行");
|
||
|
||
// 等待第一个任务完成
|
||
await task1;
|
||
}
|
||
|
||
[TestMethod]
|
||
public void Cancel_WhileExecuting_CancelsExecution()
|
||
{
|
||
// Arrange
|
||
var command = new TestCommand();
|
||
command.SetExecutionDelay(TimeSpan.FromSeconds(2));
|
||
|
||
// Act
|
||
var executeTask = command.ExecuteAsync();
|
||
command.Cancel(); // 取消执行
|
||
|
||
// Assert - 由于是异步操作,我们只验证Cancel方法不抛出异常
|
||
Assert.IsTrue(true, "Cancel方法应该能够正常调用");
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region CanExecute测试
|
||
|
||
[TestMethod]
|
||
public void CanExecute_DefaultImplementation_ReturnsSuccess()
|
||
{
|
||
// Arrange
|
||
var command = new TestCommand();
|
||
|
||
// Act
|
||
var result = command.CanExecute();
|
||
|
||
// Assert
|
||
Assert.IsTrue(result.IsSuccess, "默认的CanExecute应该返回成功");
|
||
}
|
||
|
||
[TestMethod]
|
||
public void CanExecute_WhileExecuting_ReturnsFailure()
|
||
{
|
||
// Arrange
|
||
var command = new TestCommand();
|
||
command.SetExecutionDelay(TimeSpan.FromSeconds(1));
|
||
|
||
// Act
|
||
var executeTask = command.ExecuteAsync();
|
||
var canExecuteResult = command.CanExecute();
|
||
|
||
// Assert
|
||
Assert.IsFalse(canExecuteResult.IsSuccess, "执行中的命令CanExecute应该返回失败");
|
||
|
||
// 清理
|
||
command.Cancel();
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 性能测试
|
||
|
||
[TestMethod]
|
||
public async Task ExecuteAsync_TracksElapsedTime()
|
||
{
|
||
// Arrange
|
||
var command = new TestCommand();
|
||
command.SetExecutionDelay(TimeSpan.FromMilliseconds(100));
|
||
|
||
// Act
|
||
var result = await command.ExecuteAsync();
|
||
|
||
// Assert
|
||
Assert.IsTrue(result.ElapsedMilliseconds >= 90, "执行时间应该被正确记录"); // 允许一些时间误差
|
||
Assert.IsTrue(result.ElapsedMilliseconds < 500, "执行时间不应该过长");
|
||
}
|
||
|
||
#endregion
|
||
}
|
||
|
||
#region 测试用Command实现
|
||
|
||
/// <summary>
|
||
/// 用于测试的Command实现类
|
||
/// </summary>
|
||
internal class TestCommand : CommandBase
|
||
{
|
||
private bool _shouldSucceed = true;
|
||
private bool _validationShouldFail = false;
|
||
private TimeSpan _executionDelay = TimeSpan.Zero;
|
||
|
||
public TestCommand() : base() { }
|
||
|
||
public TestCommand(string commandId, string displayName, string description)
|
||
: base(commandId, displayName, description) { }
|
||
|
||
public void SetShouldSucceed(bool shouldSucceed)
|
||
{
|
||
_shouldSucceed = shouldSucceed;
|
||
}
|
||
|
||
public void SetValidationShouldFail(bool shouldFail)
|
||
{
|
||
_validationShouldFail = shouldFail;
|
||
}
|
||
|
||
public void SetExecutionDelay(TimeSpan delay)
|
||
{
|
||
_executionDelay = delay;
|
||
}
|
||
|
||
// 暴露受保护的方法用于测试
|
||
public void SimulateProgressChange(int progress, string message)
|
||
{
|
||
UpdateProgress(progress, message);
|
||
}
|
||
|
||
public void SimulateStatusChangeEvent(CommandExecutionStatus oldStatus, CommandExecutionStatus newStatus)
|
||
{
|
||
OnStatusChanged(oldStatus, newStatus, "测试状态变化");
|
||
}
|
||
|
||
protected override async Task<PathPlanningResult> ValidateAsync(CancellationToken cancellationToken)
|
||
{
|
||
if (_validationShouldFail)
|
||
{
|
||
return PathPlanningResult.ValidationFailure("测试验证失败");
|
||
}
|
||
return PathPlanningResult.Success("验证成功");
|
||
}
|
||
|
||
protected override async Task<PathPlanningResult> ExecuteInternalAsync(CancellationToken cancellationToken)
|
||
{
|
||
// 模拟执行延迟
|
||
if (_executionDelay > TimeSpan.Zero)
|
||
{
|
||
await Task.Delay(_executionDelay, cancellationToken);
|
||
}
|
||
|
||
// 模拟进度更新
|
||
for (int i = 10; i <= 90; i += 20)
|
||
{
|
||
if (cancellationToken.IsCancellationRequested)
|
||
break;
|
||
|
||
UpdateProgress(i, $"执行进度 {i}%");
|
||
await Task.Delay(10, cancellationToken); // 短暂延迟以模拟工作
|
||
}
|
||
|
||
if (_shouldSucceed)
|
||
{
|
||
return PathPlanningResult.Success("测试执行成功");
|
||
}
|
||
else
|
||
{
|
||
return PathPlanningResult.Failure("测试执行失败");
|
||
}
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
} |