mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-03-22 10:34:30 +08:00
test(state): 添加状态机和状态相关单元测试
添加了 StateMachineTests.cs 文件,包含以下测试用例: - 验证当前状态为空时的行为 - 测试状态注册功能 - 测试状态切换功能,包括 OnEnter 和 OnExit 回调的调用 - 测试状态转换权限控制 - 测试状态注销功能 - 验证多个状态切换的回调逻辑 添加了 StateTests.cs 文件,包含以下测试用例: - 验证状态类实现 IState 接口 - 测试 OnEnter、OnExit 方法 - 测试 CanTransitionTo 转换规则 - 验证复杂状态转换逻辑 - [skip ci]
This commit is contained in:
parent
941cbee0ad
commit
4f1450fc9b
328
GFramework.Core.Tests/state/StateMachineTests.cs
Normal file
328
GFramework.Core.Tests/state/StateMachineTests.cs
Normal file
@ -0,0 +1,328 @@
|
||||
using GFramework.Core.Abstractions.state;
|
||||
using GFramework.Core.state;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace GFramework.Core.Tests.state;
|
||||
|
||||
[TestFixture]
|
||||
public class StateMachineTests
|
||||
{
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
_stateMachine = new StateMachine();
|
||||
}
|
||||
|
||||
private StateMachine _stateMachine = null!;
|
||||
|
||||
[Test]
|
||||
public void Current_Should_BeNull_When_NoState_Active()
|
||||
{
|
||||
Assert.That(_stateMachine.Current, Is.Null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Register_Should_AddState_To_StatesDictionary()
|
||||
{
|
||||
var state = new TestStateV2();
|
||||
_stateMachine.Register(state);
|
||||
|
||||
Assert.That(_stateMachine.ContainsState<TestStateV2>(), Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ChangeTo_Should_SetCurrentState()
|
||||
{
|
||||
var state = new TestStateV2();
|
||||
_stateMachine.Register(state);
|
||||
_stateMachine.ChangeTo<TestStateV2>();
|
||||
|
||||
Assert.That(_stateMachine.Current, Is.SameAs(state));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ChangeTo_Should_Invoke_OnEnter()
|
||||
{
|
||||
var state = new TestStateV2();
|
||||
_stateMachine.Register(state);
|
||||
_stateMachine.ChangeTo<TestStateV2>();
|
||||
|
||||
Assert.That(state.EnterCalled, Is.True);
|
||||
Assert.That(state.EnterFrom, Is.Null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ChangeTo_When_CurrentStateExists_Should_Invoke_OnExit()
|
||||
{
|
||||
var state1 = new TestStateV2();
|
||||
var state2 = new TestStateV3();
|
||||
_stateMachine.Register(state1);
|
||||
_stateMachine.Register(state2);
|
||||
|
||||
_stateMachine.ChangeTo<TestStateV2>();
|
||||
_stateMachine.ChangeTo<TestStateV3>();
|
||||
|
||||
Assert.That(state1.ExitCalled, Is.True);
|
||||
Assert.That(state1.ExitTo, Is.SameAs(state2));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ChangeTo_When_CurrentStateExists_Should_Invoke_OnEnter()
|
||||
{
|
||||
var state1 = new TestStateV2();
|
||||
var state2 = new TestStateV3();
|
||||
_stateMachine.Register(state1);
|
||||
_stateMachine.Register(state2);
|
||||
|
||||
_stateMachine.ChangeTo<TestStateV2>();
|
||||
_stateMachine.ChangeTo<TestStateV3>();
|
||||
|
||||
Assert.That(state2.EnterCalled, Is.True);
|
||||
Assert.That(state2.EnterFrom, Is.SameAs(state1));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ChangeTo_ToSameState_Should_NotInvoke_Callbacks()
|
||||
{
|
||||
var state = new TestStateV2();
|
||||
_stateMachine.Register(state);
|
||||
_stateMachine.ChangeTo<TestStateV2>();
|
||||
|
||||
var enterCount = state.EnterCallCount;
|
||||
var exitCount = state.ExitCallCount;
|
||||
|
||||
_stateMachine.ChangeTo<TestStateV2>();
|
||||
|
||||
Assert.That(state.EnterCallCount, Is.EqualTo(enterCount));
|
||||
Assert.That(state.ExitCallCount, Is.EqualTo(exitCount));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ChangeTo_ToUnregisteredState_Should_ThrowInvalidOperationException()
|
||||
{
|
||||
Assert.Throws<InvalidOperationException>(() => _stateMachine.ChangeTo<TestStateV2>());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CanChangeTo_WhenStateNotRegistered_Should_ReturnFalse()
|
||||
{
|
||||
var result = _stateMachine.CanChangeTo<TestStateV2>();
|
||||
Assert.That(result, Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CanChangeTo_WhenStateRegistered_Should_ReturnTrue()
|
||||
{
|
||||
var state = new TestStateV2();
|
||||
_stateMachine.Register(state);
|
||||
|
||||
var result = _stateMachine.CanChangeTo<TestStateV2>();
|
||||
Assert.That(result, Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CanChangeTo_WhenCurrentStateDeniesTransition_Should_ReturnFalse()
|
||||
{
|
||||
var state1 = new TestStateV2 { AllowTransition = false };
|
||||
var state2 = new TestStateV3();
|
||||
_stateMachine.Register(state1);
|
||||
_stateMachine.Register(state2);
|
||||
_stateMachine.ChangeTo<TestStateV2>();
|
||||
|
||||
var result = _stateMachine.CanChangeTo<TestStateV3>();
|
||||
Assert.That(result, Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ChangeTo_WhenCurrentStateDeniesTransition_Should_NotChange()
|
||||
{
|
||||
var state1 = new TestStateV2 { AllowTransition = false };
|
||||
var state2 = new TestStateV3();
|
||||
_stateMachine.Register(state1);
|
||||
_stateMachine.Register(state2);
|
||||
_stateMachine.ChangeTo<TestStateV2>();
|
||||
|
||||
var oldState = _stateMachine.Current;
|
||||
_stateMachine.ChangeTo<TestStateV3>();
|
||||
|
||||
Assert.That(_stateMachine.Current, Is.SameAs(oldState));
|
||||
Assert.That(_stateMachine.Current, Is.SameAs(state1));
|
||||
Assert.That(state2.EnterCalled, Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Unregister_Should_RemoveState_FromDictionary()
|
||||
{
|
||||
var state = new TestStateV2();
|
||||
_stateMachine.Register(state);
|
||||
_stateMachine.Unregister<TestStateV2>();
|
||||
|
||||
Assert.That(_stateMachine.ContainsState<TestStateV2>(), Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Unregister_WhenStateIsActive_Should_Invoke_OnExit_AndClearCurrent()
|
||||
{
|
||||
var state = new TestStateV2();
|
||||
_stateMachine.Register(state);
|
||||
_stateMachine.ChangeTo<TestStateV2>();
|
||||
_stateMachine.Unregister<TestStateV2>();
|
||||
|
||||
Assert.That(state.ExitCalled, Is.True);
|
||||
Assert.That(state.ExitTo, Is.Null);
|
||||
Assert.That(_stateMachine.Current, Is.Null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Unregister_WhenStateNotActive_Should_Not_Invoke_OnExit()
|
||||
{
|
||||
var state1 = new TestStateV2();
|
||||
var state2 = new TestStateV3();
|
||||
_stateMachine.Register(state1);
|
||||
_stateMachine.Register(state2);
|
||||
_stateMachine.ChangeTo<TestStateV2>();
|
||||
|
||||
_stateMachine.Unregister<TestStateV3>();
|
||||
|
||||
Assert.That(state1.ExitCalled, Is.False);
|
||||
Assert.That(_stateMachine.Current, Is.SameAs(state1));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void MultipleStateChanges_Should_Invoke_Callbacks_Correctly()
|
||||
{
|
||||
var state1 = new TestStateV2();
|
||||
var state2 = new TestStateV3();
|
||||
var state3 = new TestStateV4();
|
||||
_stateMachine.Register(state1);
|
||||
_stateMachine.Register(state2);
|
||||
_stateMachine.Register(state3);
|
||||
|
||||
_stateMachine.ChangeTo<TestStateV2>();
|
||||
_stateMachine.ChangeTo<TestStateV3>();
|
||||
_stateMachine.ChangeTo<TestStateV4>();
|
||||
|
||||
Assert.That(state1.EnterCalled, Is.True);
|
||||
Assert.That(state1.ExitCalled, Is.True);
|
||||
Assert.That(state2.EnterCalled, Is.True);
|
||||
Assert.That(state2.ExitCalled, Is.True);
|
||||
Assert.That(state3.EnterCalled, Is.True);
|
||||
Assert.That(state3.ExitCalled, Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ChangeTo_Should_Respect_CanTransitionTo_Logic()
|
||||
{
|
||||
var state1 = new TestStateV2();
|
||||
var state2 = new TestStateV3();
|
||||
var state3 = new TestStateV4();
|
||||
_stateMachine.Register(state1);
|
||||
_stateMachine.Register(state2);
|
||||
_stateMachine.Register(state3);
|
||||
|
||||
_stateMachine.ChangeTo<TestStateV2>();
|
||||
_stateMachine.ChangeTo<TestStateV3>();
|
||||
|
||||
Assert.That(state1.EnterCalled, Is.True);
|
||||
Assert.That(state1.ExitCalled, Is.True);
|
||||
Assert.That(state2.EnterCalled, Is.True);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class TestStateV2 : IState
|
||||
{
|
||||
public bool AllowTransition { get; set; } = true;
|
||||
public bool EnterCalled { get; private set; }
|
||||
public bool ExitCalled { get; private set; }
|
||||
public int EnterCallCount { get; private set; }
|
||||
public int ExitCallCount { get; private set; }
|
||||
public IState? EnterFrom { get; private set; }
|
||||
public IState? ExitTo { get; private set; }
|
||||
|
||||
public void OnEnter(IState? from)
|
||||
{
|
||||
EnterCalled = true;
|
||||
EnterCallCount++;
|
||||
EnterFrom = from;
|
||||
}
|
||||
|
||||
public void OnExit(IState? to)
|
||||
{
|
||||
ExitCalled = true;
|
||||
ExitCallCount++;
|
||||
ExitTo = to;
|
||||
}
|
||||
|
||||
public bool CanTransitionTo(IState target)
|
||||
{
|
||||
return AllowTransition;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class TestStateV3 : IState
|
||||
{
|
||||
public bool EnterCalled { get; private set; }
|
||||
public bool ExitCalled { get; private set; }
|
||||
public int EnterCallCount { get; private set; }
|
||||
public int ExitCallCount { get; private set; }
|
||||
public IState? EnterFrom { get; private set; }
|
||||
public IState? ExitTo { get; private set; }
|
||||
|
||||
public void OnEnter(IState? from)
|
||||
{
|
||||
EnterCalled = true;
|
||||
EnterCallCount++;
|
||||
EnterFrom = from;
|
||||
}
|
||||
|
||||
public void OnExit(IState? to)
|
||||
{
|
||||
ExitCalled = true;
|
||||
ExitCallCount++;
|
||||
ExitTo = to;
|
||||
}
|
||||
|
||||
public bool CanTransitionTo(IState target)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class TestStateV4 : IState
|
||||
{
|
||||
public bool EnterCalled { get; private set; }
|
||||
public bool ExitCalled { get; private set; }
|
||||
public int EnterCallCount { get; private set; }
|
||||
public int ExitCallCount { get; private set; }
|
||||
public IState? EnterFrom { get; private set; }
|
||||
public IState? ExitTo { get; private set; }
|
||||
|
||||
public void OnEnter(IState? from)
|
||||
{
|
||||
EnterCalled = true;
|
||||
EnterCallCount++;
|
||||
EnterFrom = from;
|
||||
}
|
||||
|
||||
public void OnExit(IState? to)
|
||||
{
|
||||
ExitCalled = true;
|
||||
ExitCallCount++;
|
||||
ExitTo = to;
|
||||
}
|
||||
|
||||
public bool CanTransitionTo(IState target)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public static class StateMachineExtensions
|
||||
{
|
||||
public static bool ContainsState<T>(this StateMachine stateMachine) where T : IState
|
||||
{
|
||||
return stateMachine.GetType().GetField("States", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)?
|
||||
.GetValue(stateMachine) is System.Collections.Generic.Dictionary<System.Type, IState> states &&
|
||||
states.ContainsKey(typeof(T));
|
||||
}
|
||||
}
|
||||
288
GFramework.Core.Tests/state/StateTests.cs
Normal file
288
GFramework.Core.Tests/state/StateTests.cs
Normal file
@ -0,0 +1,288 @@
|
||||
using GFramework.Core.Abstractions.state;
|
||||
using GFramework.Core.state;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace GFramework.Core.Tests.state;
|
||||
|
||||
[TestFixture]
|
||||
public class StateTests
|
||||
{
|
||||
[Test]
|
||||
public void State_Should_Implement_IState_Interface()
|
||||
{
|
||||
var state = new ConcreteStateV2();
|
||||
|
||||
Assert.That(state is IState);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void OnEnter_Should_BeCalled_When_State_Enters()
|
||||
{
|
||||
var state = new ConcreteStateV2();
|
||||
var otherState = new ConcreteStateV3();
|
||||
|
||||
state.OnEnter(otherState);
|
||||
|
||||
Assert.That(state.EnterCalled, Is.True);
|
||||
Assert.That(state.EnterFrom, Is.SameAs(otherState));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void OnEnter_WithNull_Should_Set_EnterFrom_ToNull()
|
||||
{
|
||||
var state = new ConcreteStateV2();
|
||||
|
||||
state.OnEnter(null);
|
||||
|
||||
Assert.That(state.EnterCalled, Is.True);
|
||||
Assert.That(state.EnterFrom, Is.Null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void OnExit_Should_BeCalled_When_State_Exits()
|
||||
{
|
||||
var state = new ConcreteStateV2();
|
||||
var otherState = new ConcreteStateV3();
|
||||
|
||||
state.OnExit(otherState);
|
||||
|
||||
Assert.That(state.ExitCalled, Is.True);
|
||||
Assert.That(state.ExitTo, Is.SameAs(otherState));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void OnExit_WithNull_Should_Set_ExitTo_ToNull()
|
||||
{
|
||||
var state = new ConcreteStateV2();
|
||||
|
||||
state.OnExit(null);
|
||||
|
||||
Assert.That(state.ExitCalled, Is.True);
|
||||
Assert.That(state.ExitTo, Is.Null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CanTransitionTo_WithAllowTrue_Should_ReturnTrue()
|
||||
{
|
||||
var state = new ConcreteStateV2 { AllowTransitions = true };
|
||||
var target = new ConcreteStateV3();
|
||||
|
||||
var result = state.CanTransitionTo(target);
|
||||
|
||||
Assert.That(result, Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CanTransitionTo_WithAllowFalse_Should_ReturnFalse()
|
||||
{
|
||||
var state = new ConcreteStateV2 { AllowTransitions = false };
|
||||
var target = new ConcreteStateV3();
|
||||
|
||||
var result = state.CanTransitionTo(target);
|
||||
|
||||
Assert.That(result, Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CanTransitionTo_Should_Receive_TargetState()
|
||||
{
|
||||
var state = new ConcreteStateV2 { AllowTransitions = true };
|
||||
var target = new ConcreteStateV3();
|
||||
IState? receivedTarget = null;
|
||||
|
||||
state.CanTransitionToAction = s => receivedTarget = s;
|
||||
state.CanTransitionTo(target);
|
||||
|
||||
Assert.That(receivedTarget, Is.SameAs(target));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void State_WithComplexTransitionRules_Should_Work()
|
||||
{
|
||||
var state1 = new ConditionalStateV2 { AllowedTransitions = new[] { typeof(ConcreteStateV3) } };
|
||||
var state2 = new ConcreteStateV3();
|
||||
var state3 = new ConcreteStateV4();
|
||||
|
||||
Assert.That(state1.CanTransitionTo(state2), Is.True);
|
||||
Assert.That(state1.CanTransitionTo(state3), Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void MultipleStates_Should_WorkTogether()
|
||||
{
|
||||
var state1 = new ConcreteStateV2();
|
||||
var state2 = new ConcreteStateV3();
|
||||
var state3 = new ConcreteStateV4();
|
||||
|
||||
state1.OnEnter(null);
|
||||
state2.OnEnter(state1);
|
||||
state3.OnEnter(state2);
|
||||
|
||||
Assert.That(state1.EnterCalled, Is.True);
|
||||
Assert.That(state2.EnterCalled, Is.True);
|
||||
Assert.That(state3.EnterCalled, Is.True);
|
||||
|
||||
Assert.That(state2.EnterFrom, Is.SameAs(state1));
|
||||
Assert.That(state3.EnterFrom, Is.SameAs(state2));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void State_Should_Track_MultipleTransitions()
|
||||
{
|
||||
var state = new TrackingStateV2();
|
||||
var other = new ConcreteStateV3();
|
||||
|
||||
state.OnEnter(other);
|
||||
state.OnExit(other);
|
||||
state.OnEnter(other);
|
||||
state.OnExit(null);
|
||||
|
||||
Assert.That(state.EnterCallCount, Is.EqualTo(2));
|
||||
Assert.That(state.ExitCallCount, Is.EqualTo(2));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void State_Should_Handle_SameState_Transition()
|
||||
{
|
||||
var state1 = new ConcreteStateV2();
|
||||
var state2 = new ConcreteStateV3();
|
||||
var state3 = new ConcreteStateV2();
|
||||
|
||||
state1.OnEnter(null);
|
||||
state2.OnEnter(state1);
|
||||
state3.OnEnter(state2);
|
||||
|
||||
Assert.That(state1.EnterFrom, Is.Null);
|
||||
Assert.That(state2.EnterFrom, Is.SameAs(state1));
|
||||
Assert.That(state3.EnterFrom, Is.SameAs(state2));
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class ConcreteStateV2 : IState
|
||||
{
|
||||
public bool AllowTransitions { get; set; } = true;
|
||||
public bool EnterCalled { get; private set; }
|
||||
public bool ExitCalled { get; private set; }
|
||||
public IState? EnterFrom { get; private set; }
|
||||
public IState? ExitTo { get; private set; }
|
||||
public Action<IState>? CanTransitionToAction { get; set; }
|
||||
|
||||
public void OnEnter(IState? from)
|
||||
{
|
||||
EnterCalled = true;
|
||||
EnterFrom = from;
|
||||
}
|
||||
|
||||
public void OnExit(IState? to)
|
||||
{
|
||||
ExitCalled = true;
|
||||
ExitTo = to;
|
||||
}
|
||||
|
||||
public bool CanTransitionTo(IState target)
|
||||
{
|
||||
CanTransitionToAction?.Invoke(target);
|
||||
return AllowTransitions;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class ConcreteStateV3 : IState
|
||||
{
|
||||
public bool EnterCalled { get; private set; }
|
||||
public bool ExitCalled { get; private set; }
|
||||
public IState? EnterFrom { get; private set; }
|
||||
public IState? ExitTo { get; private set; }
|
||||
|
||||
public void OnEnter(IState? from)
|
||||
{
|
||||
EnterCalled = true;
|
||||
EnterFrom = from;
|
||||
}
|
||||
|
||||
public void OnExit(IState? to)
|
||||
{
|
||||
ExitCalled = true;
|
||||
ExitTo = to;
|
||||
}
|
||||
|
||||
public bool CanTransitionTo(IState target)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class ConcreteStateV4 : IState
|
||||
{
|
||||
public bool EnterCalled { get; private set; }
|
||||
public bool ExitCalled { get; private set; }
|
||||
public IState? EnterFrom { get; private set; }
|
||||
public IState? ExitTo { get; private set; }
|
||||
|
||||
public void OnEnter(IState? from)
|
||||
{
|
||||
EnterCalled = true;
|
||||
EnterFrom = from;
|
||||
}
|
||||
|
||||
public void OnExit(IState? to)
|
||||
{
|
||||
ExitCalled = true;
|
||||
ExitTo = to;
|
||||
}
|
||||
|
||||
public bool CanTransitionTo(IState target)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class ConditionalStateV2 : IState
|
||||
{
|
||||
public Type[] AllowedTransitions { get; set; } = Array.Empty<Type>();
|
||||
public bool EnterCalled { get; private set; }
|
||||
public bool ExitCalled { get; private set; }
|
||||
public IState? EnterFrom { get; private set; }
|
||||
public IState? ExitTo { get; private set; }
|
||||
|
||||
public void OnEnter(IState? from)
|
||||
{
|
||||
EnterCalled = true;
|
||||
EnterFrom = from;
|
||||
}
|
||||
|
||||
public void OnExit(IState? to)
|
||||
{
|
||||
ExitCalled = true;
|
||||
ExitTo = to;
|
||||
}
|
||||
|
||||
public bool CanTransitionTo(IState target)
|
||||
{
|
||||
return AllowedTransitions.Contains(target.GetType());
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class TrackingStateV2 : IState
|
||||
{
|
||||
public int EnterCallCount { get; private set; }
|
||||
public int ExitCallCount { get; private set; }
|
||||
public IState? EnterFrom { get; private set; }
|
||||
public IState? ExitTo { get; private set; }
|
||||
|
||||
public void OnEnter(IState? from)
|
||||
{
|
||||
EnterCallCount++;
|
||||
EnterFrom = from;
|
||||
}
|
||||
|
||||
public void OnExit(IState? to)
|
||||
{
|
||||
ExitCallCount++;
|
||||
ExitTo = to;
|
||||
}
|
||||
|
||||
public bool CanTransitionTo(IState target)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user