GFramework/GFramework.Core.Tests/property/BindablePropertyTests.cs
GeWuYou f984f4a600 refactor(core): 优化核心组件的线程安全性和错误处理
- 重构 AsyncLogAppender 的 Flush 方法,添加超时控制和 SpinWait 优化
- 为 BindableProperty 添加线程安全锁保护,确保并发访问的安全性
- 在 BindableProperty 中实现回调外部调用以避免死锁问题
- 为 EasyEvents 使用 ConcurrentDictionary 替代 Dictionary 提高并发性能
- 添加协程调度器异常处理回调机制,防止异常传播导致调度器崩溃
- 为 FileAppender 添加初始化异常处理和资源清理逻辑
- 补充完整的单元测试覆盖并发场景下的线程安全性验证
2026-03-04 11:04:59 +08:00

348 lines
9.5 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 GFramework.Core.property;
using NUnit.Framework;
namespace GFramework.Core.Tests.property;
/// <summary>
/// BindableProperty类的单元测试
/// </summary>
[TestFixture]
public class BindablePropertyTests
{
/// <summary>
/// 测试清理方法,在每个测试方法执行后重置比较器
/// </summary>
[TearDown]
public void TearDown()
{
BindableProperty<string>.Comparer = (a, b) => a?.Equals(b) ?? b == null;
}
/// <summary>
/// 测试获取值时应返回默认值
/// </summary>
[Test]
public void Value_Get_Should_Return_Default_Value()
{
var property = new BindableProperty<int>(5);
Assert.That(property.Value, Is.EqualTo(5));
}
/// <summary>
/// 测试设置值时应触发事件
/// </summary>
[Test]
public void Value_Set_Should_Trigger_Event()
{
var property = new BindableProperty<int>();
var receivedValue = 0;
property.Register(value => { receivedValue = value; });
property.Value = 42;
Assert.That(receivedValue, Is.EqualTo(42));
}
/// <summary>
/// 测试设置相同值时不触发事件
/// </summary>
[Test]
public void Value_Set_To_Same_Value_Should_Not_Trigger_Event()
{
var property = new BindableProperty<int>(5);
var count = 0;
property.Register(_ => { count++; });
property.Value = 5;
Assert.That(count, Is.EqualTo(0));
}
/// <summary>
/// 测试取消注册应移除处理器
/// </summary>
[Test]
public void UnRegister_Should_Remove_Handler()
{
var property = new BindableProperty<int>();
var count = 0;
Action<int> handler = _ => { count++; };
property.Register(handler);
property.Value = 1;
Assert.That(count, Is.EqualTo(1));
property.UnRegister(handler);
property.Value = 2;
Assert.That(count, Is.EqualTo(1));
}
/// <summary>
/// 测试带初始值注册应立即调用处理器
/// </summary>
[Test]
public void RegisterWithInitValue_Should_Call_Handler_Immediately()
{
var property = new BindableProperty<int>(5);
var receivedValue = 0;
property.RegisterWithInitValue(value => { receivedValue = value; });
Assert.That(receivedValue, Is.EqualTo(5));
}
/// <summary>
/// 测试无事件设置值不应触发事件
/// </summary>
[Test]
public void SetValueWithoutEvent_Should_Not_Trigger_Event()
{
var property = new BindableProperty<int>();
var count = 0;
property.Register(_ => { count++; });
property.SetValueWithoutEvent(42);
Assert.That(count, Is.EqualTo(0));
Assert.That(property.Value, Is.EqualTo(42));
}
/// <summary>
/// 测试使用自定义比较器
/// </summary>
[Test]
public void WithComparer_Should_Use_Custom_Comparer()
{
var comparerWasCalled = false;
var comparisonResult = false;
// 设置自定义比较器
BindableProperty<string>.Comparer = (a, b) =>
{
comparerWasCalled = true;
comparisonResult = a.Length == b.Length;
return comparisonResult;
};
var property = new BindableProperty<string>("test");
var count = 0;
property.Register(_ => { count++; });
property.Value = "test";
Assert.That(comparerWasCalled, Is.True, "自定义比较器应该被调用");
Assert.That(comparisonResult, Is.True, "比较结果应该是true相同长度");
Assert.That(count, Is.EqualTo(0), "不应该触发事件");
}
/// <summary>
/// 测试多个处理器都应被调用
/// </summary>
[Test]
public void Multiple_Handlers_Should_All_Be_Called()
{
var property = new BindableProperty<int>();
var count1 = 0;
var count2 = 0;
property.Register(_ => { count1++; });
property.Register(_ => { count2++; });
property.Value = 42;
Assert.That(count1, Is.EqualTo(1));
Assert.That(count2, Is.EqualTo(1));
}
/// <summary>
/// 测试注册应返回IUnRegister接口
/// </summary>
[Test]
public void Register_Should_Return_IUnRegister()
{
var property = new BindableProperty<int>();
var unRegister = property.Register(_ => { });
Assert.That(unRegister, Is.Not.Null);
}
/// <summary>
/// 测试ToString应返回值的字符串表示
/// </summary>
[Test]
public void ToString_Should_Return_Value_As_String()
{
var property = new BindableProperty<int>(42);
var result = property.ToString();
Assert.That(result, Is.EqualTo("42"));
}
/// <summary>
/// 测试并发场景下的属性值设置
/// </summary>
[Test]
public void Concurrent_Value_Set_Should_Be_Thread_Safe()
{
var property = new BindableProperty<int>(0);
const int threadCount = 10;
const int iterationsPerThread = 100;
var tasks = new Task[threadCount];
var exceptions = new List<Exception>();
for (var i = 0; i < threadCount; i++)
{
var threadId = i;
tasks[i] = Task.Run(() =>
{
try
{
for (var j = 0; j < iterationsPerThread; j++)
{
property.Value = threadId * iterationsPerThread + j;
}
}
catch (Exception ex)
{
lock (exceptions)
{
exceptions.Add(ex);
}
}
});
}
Task.WaitAll(tasks);
Assert.That(exceptions, Is.Empty, $"并发测试中发生异常: {string.Join(", ", exceptions.Select(e => e.Message))}");
}
/// <summary>
/// 测试并发场景下的事件注册和触发
/// </summary>
[Test]
public void Concurrent_Register_And_Trigger_Should_Be_Thread_Safe()
{
var property = new BindableProperty<int>(0);
const int threadCount = 5;
var tasks = new Task[threadCount];
var exceptions = new List<Exception>();
var callCounts = new int[threadCount];
for (var i = 0; i < threadCount; i++)
{
var threadId = i;
tasks[i] = Task.Run(() =>
{
try
{
property.Register(value => { Interlocked.Increment(ref callCounts[threadId]); });
}
catch (Exception ex)
{
lock (exceptions)
{
exceptions.Add(ex);
}
}
});
}
Task.WaitAll(tasks);
// 触发事件
property.Value = 42;
Assert.That(exceptions, Is.Empty);
Assert.That(callCounts.Sum(), Is.EqualTo(threadCount), "所有注册的处理器都应该被调用");
}
/// <summary>
/// 测试并发场景下的注册和取消注册
/// </summary>
[Test]
public void Concurrent_Register_And_UnRegister_Should_Be_Thread_Safe()
{
var property = new BindableProperty<int>(0);
const int threadCount = 10;
const int iterationsPerThread = 50;
var tasks = new Task[threadCount];
var exceptions = new List<Exception>();
for (var i = 0; i < threadCount; i++)
{
tasks[i] = Task.Run(() =>
{
try
{
for (var j = 0; j < iterationsPerThread; j++)
{
Action<int> handler = _ => { };
property.Register(handler);
property.UnRegister(handler);
}
}
catch (Exception ex)
{
lock (exceptions)
{
exceptions.Add(ex);
}
}
});
}
Task.WaitAll(tasks);
Assert.That(exceptions, Is.Empty);
}
/// <summary>
/// 测试并发场景下RegisterWithInitValue的线程安全性
/// </summary>
[Test]
public void Concurrent_RegisterWithInitValue_Should_Be_Thread_Safe()
{
var property = new BindableProperty<int>(42);
const int threadCount = 10;
var tasks = new Task[threadCount];
var exceptions = new List<Exception>();
var receivedValues = new List<int>();
for (var i = 0; i < threadCount; i++)
{
tasks[i] = Task.Run(() =>
{
try
{
property.RegisterWithInitValue(value =>
{
lock (receivedValues)
{
receivedValues.Add(value);
}
});
}
catch (Exception ex)
{
lock (exceptions)
{
exceptions.Add(ex);
}
}
});
}
Task.WaitAll(tasks);
Assert.That(exceptions, Is.Empty);
Assert.That(receivedValues.Count, Is.EqualTo(threadCount));
Assert.That(receivedValues.All(v => v == 42), Is.True, "所有初始值都应该是42");
}
}