632 lines
19 KiB
C#
632 lines
19 KiB
C#
namespace Common.Test;
|
|
|
|
/// <summary>
|
|
/// Global 轻量级 DI 容器测试
|
|
/// 展示了依赖注入容器的所有功能和使用场景
|
|
/// </summary>
|
|
[TestClass]
|
|
public class GlobalTests
|
|
{
|
|
/// <summary>
|
|
/// 每个测试方法执行前清除所有注册的服务,确保测试隔离
|
|
/// </summary>
|
|
[TestInitialize]
|
|
public void Setup()
|
|
{
|
|
Global.Clear();
|
|
}
|
|
|
|
#region 基础注册和获取测试
|
|
|
|
/// <summary>
|
|
/// 测试:注册并获取单例服务实例
|
|
/// 演示最基本的服务注册和获取
|
|
/// </summary>
|
|
[TestMethod]
|
|
public void RegisterSingleton_WithInstance_CanGetSameInstance()
|
|
{
|
|
// Arrange: 创建一个配置对象
|
|
var config = new AppConfig { AppName = "MyApp", Version = "1.0" };
|
|
|
|
// Act: 注册为单例
|
|
Global.RegisterSingleton(config);
|
|
var retrieved = Global.Get<AppConfig>();
|
|
|
|
// Assert: 应该返回同一个实例
|
|
Assert.AreSame(config, retrieved);
|
|
Assert.AreEqual("MyApp", retrieved.AppName);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 测试:使用工厂方法注册单例
|
|
/// 演示延迟初始化,只有在第一次 Get 时才会调用工厂方法
|
|
/// </summary>
|
|
[TestMethod]
|
|
public void RegisterSingleton_WithFactory_CreatesInstanceOnFirstGet()
|
|
{
|
|
// Arrange: 创建一个计数器来跟踪工厂方法被调用的次数
|
|
var factoryCallCount = 0;
|
|
|
|
// Act: 使用工厂方法注册
|
|
Global.RegisterSingleton(() =>
|
|
{
|
|
factoryCallCount++;
|
|
return new AppConfig { AppName = "FactoryApp" };
|
|
});
|
|
|
|
// Assert: 工厂方法还未被调用
|
|
Assert.AreEqual(0, factoryCallCount);
|
|
|
|
// Act: 第一次获取
|
|
var first = Global.Get<AppConfig>();
|
|
Assert.AreEqual(1, factoryCallCount); // 工厂方法被调用了一次
|
|
|
|
// Act: 第二次获取
|
|
var second = Global.Get<AppConfig>();
|
|
Assert.AreEqual(1, factoryCallCount); // 工厂方法没有再次调用
|
|
Assert.AreSame(first, second); // 返回的是同一个实例
|
|
}
|
|
|
|
/// <summary>
|
|
/// 测试:注册瞬态服务,每次获取都创建新实例
|
|
/// </summary>
|
|
[TestMethod]
|
|
public void RegisterTransient_CreatesNewInstanceEachTime()
|
|
{
|
|
// Arrange & Act: 注册瞬态服务
|
|
Global.RegisterTransient(() => new RequestContext
|
|
{
|
|
RequestId = Guid.NewGuid().ToString(),
|
|
Timestamp = DateTime.Now
|
|
});
|
|
|
|
// Act: 多次获取
|
|
var first = Global.Get<RequestContext>();
|
|
var second = Global.Get<RequestContext>();
|
|
var third = Global.Get<RequestContext>();
|
|
|
|
// Assert: 每次都是不同的实例
|
|
Assert.AreNotSame(first, second);
|
|
Assert.AreNotSame(second, third);
|
|
Assert.AreNotEqual(first.RequestId, second.RequestId);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 测试:使用通用 Register 方法指定生命周期
|
|
/// </summary>
|
|
[TestMethod]
|
|
public void Register_WithLifetime_CreatesCorrectLifetime()
|
|
{
|
|
// Arrange & Act: 使用通用方法注册单例
|
|
Global.Register(() => new CacheService(), ServiceLifetime.Singleton);
|
|
var cache1 = Global.Get<CacheService>();
|
|
var cache2 = Global.Get<CacheService>();
|
|
|
|
// Assert: 单例应该是同一个实例
|
|
Assert.AreSame(cache1, cache2);
|
|
|
|
// Arrange & Act: 使用通用方法注册瞬态
|
|
Global.Register(() => new OperationContext(), ServiceLifetime.Transient);
|
|
var op1 = Global.Get<OperationContext>();
|
|
var op2 = Global.Get<OperationContext>();
|
|
|
|
// Assert: 瞬态应该是不同实例
|
|
Assert.AreNotSame(op1, op2);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region 服务获取测试
|
|
|
|
/// <summary>
|
|
/// 测试:获取未注册的服务会抛出异常
|
|
/// </summary>
|
|
[TestMethod]
|
|
public void Get_UnregisteredService_ThrowsException()
|
|
{
|
|
// Act & Assert: 尝试获取未注册的服务应该抛出异常
|
|
var exception = Assert.ThrowsException<InvalidOperationException>(() => { Global.Get<ILogger>(); });
|
|
|
|
Assert.IsTrue(exception.Message.Contains("ILogger"));
|
|
}
|
|
|
|
/// <summary>
|
|
/// 测试:TryGet 安全地尝试获取服务
|
|
/// </summary>
|
|
[TestMethod]
|
|
public void TryGet_UnregisteredService_ReturnsFalse()
|
|
{
|
|
// Act: 尝试获取未注册的服务
|
|
var success = Global.TryGet<ILogger>(out var logger);
|
|
|
|
// Assert: 应该返回 false 且 logger 为 null
|
|
Assert.IsFalse(success);
|
|
Assert.IsNull(logger);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 测试:TryGet 成功获取已注册的服务
|
|
/// </summary>
|
|
[TestMethod]
|
|
public void TryGet_RegisteredService_ReturnsTrue()
|
|
{
|
|
// Arrange: 注册服务
|
|
var expectedLogger = new ConsoleLogger();
|
|
Global.RegisterSingleton<ILogger>(expectedLogger);
|
|
|
|
// Act: 尝试获取
|
|
var success = Global.TryGet<ILogger>(out var logger);
|
|
|
|
// Assert: 应该成功获取
|
|
Assert.IsTrue(success);
|
|
Assert.IsNotNull(logger);
|
|
Assert.AreSame(expectedLogger, logger);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 测试:GetOrDefault 获取服务或返回默认值
|
|
/// </summary>
|
|
[TestMethod]
|
|
public void GetOrDefault_UnregisteredService_ReturnsDefault()
|
|
{
|
|
// Arrange: 创建默认值
|
|
var defaultLogger = new ConsoleLogger();
|
|
|
|
// Act: 获取未注册的服务
|
|
var logger = Global.GetOrDefault(defaultLogger);
|
|
|
|
// Assert: 应该返回默认值
|
|
Assert.AreSame(defaultLogger, logger);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 测试:GetOrDefault 获取已注册的服务而不是默认值
|
|
/// </summary>
|
|
[TestMethod]
|
|
public void GetOrDefault_RegisteredService_ReturnsService()
|
|
{
|
|
// Arrange: 注册服务
|
|
var registeredLogger = new ConsoleLogger();
|
|
Global.RegisterSingleton<ILogger>(registeredLogger);
|
|
var defaultLogger = new ConsoleLogger();
|
|
|
|
// Act: 获取服务
|
|
var logger = Global.GetOrDefault(defaultLogger);
|
|
|
|
// Assert: 应该返回注册的服务,而不是默认值
|
|
Assert.AreSame(registeredLogger, logger);
|
|
Assert.AreNotSame(defaultLogger, logger);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region 服务更新和变更通知测试
|
|
|
|
/// <summary>
|
|
/// 测试:更新单例服务实例
|
|
/// </summary>
|
|
[TestMethod]
|
|
public void Update_SingletonService_UpdatesInstance()
|
|
{
|
|
// Arrange: 注册初始配置
|
|
var oldConfig = new AppConfig { AppName = "OldApp", Version = "1.0" };
|
|
Global.RegisterSingleton(oldConfig);
|
|
|
|
// Act: 更新配置
|
|
var newConfig = new AppConfig { AppName = "NewApp", Version = "2.0" };
|
|
Global.Update(newConfig);
|
|
|
|
// Assert: 获取的应该是新配置
|
|
var retrieved = Global.Get<AppConfig>();
|
|
Assert.AreSame(newConfig, retrieved);
|
|
Assert.AreEqual("NewApp", retrieved.AppName);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 测试:更新瞬态服务会抛出异常
|
|
/// </summary>
|
|
[TestMethod]
|
|
public void Update_TransientService_ThrowsException()
|
|
{
|
|
// Arrange: 注册瞬态服务
|
|
Global.RegisterTransient(() => new RequestContext());
|
|
|
|
// Act & Assert: 尝试更新瞬态服务应该抛出异常
|
|
Assert.ThrowsException<InvalidOperationException>(() => { Global.Update(new RequestContext()); });
|
|
}
|
|
|
|
/// <summary>
|
|
/// 测试:订阅服务变更通知
|
|
/// 演示如何监听服务变更事件
|
|
/// </summary>
|
|
[TestMethod]
|
|
public void Subscribe_ServiceChange_ReceivesNotification()
|
|
{
|
|
// Arrange: 设置事件处理器
|
|
ServiceChangedEventArgs<AppConfig>? receivedEventArgs = null;
|
|
var callCount = 0;
|
|
|
|
Global.Subscribe<AppConfig>(e =>
|
|
{
|
|
receivedEventArgs = e;
|
|
callCount++;
|
|
});
|
|
|
|
// Act: 注册初始服务
|
|
var oldConfig = new AppConfig { AppName = "OldApp", Version = "1.0" };
|
|
Global.RegisterSingleton(oldConfig);
|
|
|
|
// 第一次注册不会触发通知(因为没有旧值)
|
|
Assert.AreEqual(0, callCount);
|
|
|
|
// Act: 更新服务
|
|
var newConfig = new AppConfig { AppName = "NewApp", Version = "2.0" };
|
|
Global.Update(newConfig);
|
|
|
|
// Assert: 应该收到通知
|
|
Assert.AreEqual(1, callCount);
|
|
Assert.IsNotNull(receivedEventArgs);
|
|
Assert.AreSame(oldConfig, receivedEventArgs.OldValue);
|
|
Assert.AreSame(newConfig, receivedEventArgs.NewValue);
|
|
Assert.AreEqual("OldApp", receivedEventArgs.OldValue?.AppName);
|
|
Assert.AreEqual("NewApp", receivedEventArgs.NewValue.AppName);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 测试:多个订阅者都能收到通知
|
|
/// </summary>
|
|
[TestMethod]
|
|
public void Subscribe_MultipleSubscribers_AllReceiveNotifications()
|
|
{
|
|
// Arrange: 创建多个订阅者
|
|
var subscriber1Called = false;
|
|
var subscriber2Called = false;
|
|
var subscriber3Called = false;
|
|
|
|
Global.Subscribe<AppConfig>(e => subscriber1Called = true);
|
|
Global.Subscribe<AppConfig>(e => subscriber2Called = true);
|
|
Global.Subscribe<AppConfig>(e => subscriber3Called = true);
|
|
|
|
// Act: 注册并更新服务
|
|
Global.RegisterSingleton(new AppConfig { AppName = "App", Version = "1.0" });
|
|
Global.Update(new AppConfig { AppName = "App", Version = "2.0" });
|
|
|
|
// Assert: 所有订阅者都应该收到通知
|
|
Assert.IsTrue(subscriber1Called);
|
|
Assert.IsTrue(subscriber2Called);
|
|
Assert.IsTrue(subscriber3Called);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 测试:取消订阅后不再收到通知
|
|
/// </summary>
|
|
[TestMethod]
|
|
public void Unsubscribe_RemovesSubscriber()
|
|
{
|
|
// Arrange: 订阅通知
|
|
var callCount = 0;
|
|
|
|
void Handler(ServiceChangedEventArgs<AppConfig> e)
|
|
{
|
|
callCount++;
|
|
}
|
|
|
|
Global.Subscribe<AppConfig>(Handler);
|
|
Global.RegisterSingleton(new AppConfig { AppName = "App", Version = "1.0" });
|
|
|
|
// Act: 更新一次(应该收到通知)
|
|
Global.Update(new AppConfig { AppName = "App", Version = "2.0" });
|
|
Assert.AreEqual(1, callCount);
|
|
|
|
// Act: 取消订阅
|
|
Global.Unsubscribe<AppConfig>(Handler);
|
|
|
|
// Act: 再次更新(不应该收到通知)
|
|
Global.Update(new AppConfig { AppName = "App", Version = "3.0" });
|
|
Assert.AreEqual(1, callCount); // 计数没有增加
|
|
}
|
|
|
|
/// <summary>
|
|
/// 测试:注册时启用 notifyIfExists 会触发通知
|
|
/// </summary>
|
|
[TestMethod]
|
|
public void RegisterSingleton_WithNotifyIfExists_TriggersNotification()
|
|
{
|
|
// Arrange: 订阅通知
|
|
var notificationCount = 0;
|
|
Global.Subscribe<AppConfig>(e => notificationCount++);
|
|
|
|
// Act: 第一次注册
|
|
Global.RegisterSingleton(new AppConfig { AppName = "App", Version = "1.0" });
|
|
Assert.AreEqual(0, notificationCount); // 首次注册不触发
|
|
|
|
// Act: 使用 notifyIfExists = true 再次注册
|
|
Global.RegisterSingleton(new AppConfig { AppName = "App", Version = "2.0" }, true);
|
|
|
|
// Assert: 应该触发通知
|
|
Assert.AreEqual(1, notificationCount);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region 服务管理测试
|
|
|
|
/// <summary>
|
|
/// 测试:检查服务是否已注册
|
|
/// </summary>
|
|
[TestMethod]
|
|
public void IsRegistered_ChecksServiceRegistration()
|
|
{
|
|
// Assert: 初始未注册
|
|
Assert.IsFalse(Global.IsRegistered<AppConfig>());
|
|
|
|
// Act: 注册服务
|
|
Global.RegisterSingleton(new AppConfig());
|
|
|
|
// Assert: 现在已注册
|
|
Assert.IsTrue(Global.IsRegistered<AppConfig>());
|
|
}
|
|
|
|
/// <summary>
|
|
/// 测试:注销服务
|
|
/// </summary>
|
|
[TestMethod]
|
|
public void Unregister_RemovesService()
|
|
{
|
|
// Arrange: 注册服务
|
|
Global.RegisterSingleton(new AppConfig { AppName = "TestApp" });
|
|
Assert.IsTrue(Global.IsRegistered<AppConfig>());
|
|
|
|
// Act: 注销服务
|
|
var result = Global.Unregister<AppConfig>();
|
|
|
|
// Assert: 服务已被移除
|
|
Assert.IsTrue(result);
|
|
Assert.IsFalse(Global.IsRegistered<AppConfig>());
|
|
|
|
// 尝试获取应该失败
|
|
Assert.ThrowsException<InvalidOperationException>(() => { Global.Get<AppConfig>(); });
|
|
}
|
|
|
|
/// <summary>
|
|
/// 测试:清除所有服务
|
|
/// </summary>
|
|
[TestMethod]
|
|
public void Clear_RemovesAllServices()
|
|
{
|
|
// Arrange: 注册多个服务
|
|
Global.RegisterSingleton(new AppConfig());
|
|
Global.RegisterSingleton<ILogger>(new ConsoleLogger());
|
|
Global.RegisterTransient(() => new RequestContext());
|
|
|
|
Assert.AreEqual(3, Global.Count);
|
|
|
|
// Act: 清除所有服务
|
|
Global.Clear();
|
|
|
|
// Assert: 所有服务都被移除
|
|
Assert.AreEqual(0, Global.Count);
|
|
Assert.IsFalse(Global.IsRegistered<AppConfig>());
|
|
Assert.IsFalse(Global.IsRegistered<ILogger>());
|
|
}
|
|
|
|
/// <summary>
|
|
/// 测试:获取已注册服务的数量
|
|
/// </summary>
|
|
[TestMethod]
|
|
public void Count_ReturnsCorrectNumber()
|
|
{
|
|
// Assert: 初始为 0
|
|
Assert.AreEqual(0, Global.Count);
|
|
|
|
// Act: 注册服务
|
|
Global.RegisterSingleton(new AppConfig());
|
|
Assert.AreEqual(1, Global.Count);
|
|
|
|
Global.RegisterSingleton<ILogger>(new ConsoleLogger());
|
|
Assert.AreEqual(2, Global.Count);
|
|
|
|
Global.RegisterTransient(() => new RequestContext());
|
|
Assert.AreEqual(3, Global.Count);
|
|
|
|
// Act: 注销一个服务
|
|
Global.Unregister<AppConfig>();
|
|
Assert.AreEqual(2, Global.Count);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region 线程安全测试
|
|
|
|
/// <summary>
|
|
/// 测试:多线程并发获取单例服务,确保只创建一次
|
|
/// 演示容器的线程安全性
|
|
/// </summary>
|
|
[TestMethod]
|
|
public void Get_ConcurrentAccess_CreatesSingletonOnce()
|
|
{
|
|
// Arrange: 使用计数器追踪工厂方法调用次数
|
|
var factoryCallCount = 0;
|
|
var createdInstances = new HashSet<int>();
|
|
|
|
Global.RegisterSingleton(() =>
|
|
{
|
|
Interlocked.Increment(ref factoryCallCount);
|
|
Thread.Sleep(50); // 模拟复杂初始化
|
|
return new CacheService();
|
|
});
|
|
|
|
// Act: 多线程并发获取
|
|
var tasks = Enumerable.Range(0, 20).Select(i =>
|
|
Task.Run(() =>
|
|
{
|
|
var service = Global.Get<CacheService>();
|
|
lock (createdInstances)
|
|
{
|
|
createdInstances.Add(service.GetHashCode());
|
|
}
|
|
})
|
|
).ToArray();
|
|
|
|
Task.WaitAll(tasks);
|
|
|
|
// Assert: 工厂方法只被调用一次,所有线程获取的是同一个实例
|
|
Assert.AreEqual(1, factoryCallCount);
|
|
Assert.AreEqual(1, createdInstances.Count);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 测试:多线程并发订阅和更新
|
|
/// </summary>
|
|
[TestMethod]
|
|
public void Subscribe_ConcurrentSubscribeAndUpdate_ThreadSafe()
|
|
{
|
|
// Arrange: 注册服务
|
|
Global.RegisterSingleton(new AppConfig { AppName = "App", Version = "1.0" });
|
|
|
|
var notificationCounts = new System.Collections.Concurrent.ConcurrentBag<int>();
|
|
|
|
// Act: 多线程并发订阅
|
|
var subscribeTasks = Enumerable.Range(0, 10).Select(i =>
|
|
Task.Run(() =>
|
|
{
|
|
var count = 0;
|
|
Global.Subscribe<AppConfig>(e => Interlocked.Increment(ref count));
|
|
notificationCounts.Add(count);
|
|
})
|
|
).ToArray();
|
|
|
|
Task.WaitAll(subscribeTasks);
|
|
|
|
// Act: 触发更新
|
|
Global.Update(new AppConfig { AppName = "App", Version = "2.0" });
|
|
|
|
// 等待所有通知处理完成
|
|
Thread.Sleep(100);
|
|
|
|
// Assert: 不应该抛出异常,所有订阅者都应该收到通知
|
|
Assert.AreEqual(10, notificationCounts.Count);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region 实际应用场景测试
|
|
|
|
/// <summary>
|
|
/// 场景测试:应用配置热更新
|
|
/// 演示实时配置更新的完整流程
|
|
/// </summary>
|
|
[TestMethod]
|
|
public void Scenario_LiveConfigurationUpdate()
|
|
{
|
|
// Arrange: 模拟配置变更历史
|
|
var configHistory = new List<string>();
|
|
|
|
// 订阅配置变更
|
|
Global.Subscribe<AppConfig>(e => { configHistory.Add($"v{e.OldValue?.Version} -> v{e.NewValue.Version}"); });
|
|
|
|
// Act: 注册初始配置
|
|
Global.RegisterSingleton(new AppConfig { AppName = "MyApp", Version = "1.0" });
|
|
|
|
// 模拟配置文件更新
|
|
Global.Update(new AppConfig { AppName = "MyApp", Version = "1.1" });
|
|
Global.Update(new AppConfig { AppName = "MyApp", Version = "1.2" });
|
|
Global.Update(new AppConfig { AppName = "MyApp", Version = "2.0" });
|
|
|
|
// Assert: 应该记录所有变更
|
|
Assert.AreEqual(3, configHistory.Count);
|
|
Assert.AreEqual("v1.0 -> v1.1", configHistory[0]);
|
|
Assert.AreEqual("v1.1 -> v1.2", configHistory[1]);
|
|
Assert.AreEqual("v1.2 -> v2.0", configHistory[2]);
|
|
|
|
// 当前配置应该是最新的
|
|
var currentConfig = Global.Get<AppConfig>();
|
|
Assert.AreEqual("2.0", currentConfig.Version);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 场景测试:用户会话管理
|
|
/// 演示多组件监听用户会话变更
|
|
/// </summary>
|
|
[TestMethod]
|
|
public void Scenario_UserSessionManagement()
|
|
{
|
|
// Arrange: 模拟不同组件
|
|
var uiUpdates = new List<string>();
|
|
var logEntries = new List<string>();
|
|
|
|
// UI 组件订阅
|
|
Global.Subscribe<UserSession>(e => { uiUpdates.Add($"Display: {e.NewValue.Username}"); });
|
|
|
|
// 日志组件订阅
|
|
Global.Subscribe<UserSession>(e => { logEntries.Add($"Session changed at {e.ChangedAt:HH:mm:ss}"); });
|
|
|
|
// Act: 用户登录
|
|
Global.RegisterSingleton(new UserSession
|
|
{
|
|
Username = "user1",
|
|
LoginTime = DateTime.Now
|
|
});
|
|
|
|
// 用户切换
|
|
Thread.Sleep(10);
|
|
Global.Update(new UserSession
|
|
{
|
|
Username = "user2",
|
|
LoginTime = DateTime.Now
|
|
});
|
|
|
|
// Assert: 各组件都应该收到通知
|
|
Assert.AreEqual(1, uiUpdates.Count);
|
|
Assert.AreEqual("Display: user2", uiUpdates[0]);
|
|
Assert.AreEqual(1, logEntries.Count);
|
|
Assert.IsTrue(logEntries[0].StartsWith("Session changed at"));
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region 测试辅助类型
|
|
|
|
private class AppConfig
|
|
{
|
|
public string AppName { get; set; } = "";
|
|
public string Version { get; set; } = "";
|
|
}
|
|
|
|
private class RequestContext
|
|
{
|
|
public string RequestId { get; set; } = Guid.NewGuid().ToString();
|
|
public DateTime Timestamp { get; set; } = DateTime.Now;
|
|
}
|
|
|
|
private interface ILogger
|
|
{
|
|
void Log(string message);
|
|
}
|
|
|
|
private class ConsoleLogger : ILogger
|
|
{
|
|
public void Log(string message)
|
|
{
|
|
Console.WriteLine($"[LOG] {message}");
|
|
}
|
|
}
|
|
|
|
private class UserSession
|
|
{
|
|
public string Username { get; set; } = "";
|
|
public DateTime LoginTime { get; set; }
|
|
}
|
|
|
|
private class CacheService
|
|
{
|
|
private readonly Dictionary<string, object> _cache = new();
|
|
}
|
|
|
|
private class OperationContext
|
|
{
|
|
public Guid OperationId { get; } = Guid.NewGuid();
|
|
}
|
|
|
|
#endregion
|
|
} |