NavisworksTransport/UnitTests/Commands/CommandBaseTests.cs

434 lines
14 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
}