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