659 lines
18 KiB
C#
659 lines
18 KiB
C#
using System.ComponentModel;
|
|
using System.Reflection;
|
|
using Common.Utils;
|
|
|
|
namespace Common.Test;
|
|
|
|
/// <summary>
|
|
/// ReflectionHelper 反射工具测试
|
|
/// 展示类型扫描、属性访问、特性读取等功能
|
|
/// </summary>
|
|
[TestClass]
|
|
public class ReflectionHelperTests
|
|
{
|
|
#region 测试类定义
|
|
|
|
/// <summary>
|
|
/// 测试基类
|
|
/// </summary>
|
|
public abstract class BaseEntity
|
|
{
|
|
public int Id { get; set; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// 测试接口
|
|
/// </summary>
|
|
public interface ITestService
|
|
{
|
|
void Execute();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 测试特性
|
|
/// </summary>
|
|
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property)]
|
|
public class TestAttribute : Attribute
|
|
{
|
|
public string Name { get; set; }
|
|
|
|
public TestAttribute(string name)
|
|
{
|
|
Name = name;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 测试实体类
|
|
/// </summary>
|
|
[Test("UserEntity")]
|
|
public class User : BaseEntity
|
|
{
|
|
[Test("UserName")]
|
|
[System.ComponentModel.Description("用户名")]
|
|
public string Name { get; set; } = "";
|
|
|
|
[System.ComponentModel.Description("用户年龄")]
|
|
public int Age { get; set; }
|
|
|
|
public string Email { get; set; } = "";
|
|
|
|
public DateTime CreatedAt { get; set; } = DateTime.Now;
|
|
|
|
public void SayHello()
|
|
{
|
|
Console.WriteLine($"Hello, I'm {Name}");
|
|
}
|
|
|
|
public static string GetVersion()
|
|
{
|
|
return "1.0.0";
|
|
}
|
|
|
|
public int Calculate(int a, int b)
|
|
{
|
|
return a + b;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 测试 DTO
|
|
/// </summary>
|
|
public class UserDto
|
|
{
|
|
public string Name { get; set; } = "";
|
|
public int Age { get; set; }
|
|
public string Email { get; set; } = "";
|
|
}
|
|
|
|
/// <summary>
|
|
/// 测试服务实现
|
|
/// </summary>
|
|
public class TestService : ITestService
|
|
{
|
|
public void Execute()
|
|
{
|
|
Console.WriteLine("Executing test service");
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region 类型扫描测试
|
|
|
|
[TestMethod]
|
|
public void FindSubclassesOf_FindsAllSubclasses()
|
|
{
|
|
// Act
|
|
var subclasses = ReflectionHelper.FindSubclassesOf<BaseEntity>();
|
|
|
|
// Assert
|
|
Assert.IsTrue(subclasses.Count > 0);
|
|
Assert.IsTrue(subclasses.Any(t => t == typeof(User)));
|
|
}
|
|
|
|
[TestMethod]
|
|
public void FindImplementationsOf_FindsAllImplementations()
|
|
{
|
|
// Arrange
|
|
var assembly = Assembly.GetExecutingAssembly();
|
|
|
|
// Act
|
|
var implementations = ReflectionHelper.FindImplementationsOf<ITestService>(assembly);
|
|
|
|
// Assert
|
|
Assert.IsTrue(implementations.Count > 0);
|
|
Assert.IsTrue(implementations.Any(t => t == typeof(TestService)));
|
|
}
|
|
|
|
[TestMethod]
|
|
public void FindTypesWithAttribute_FindsTypesWithAttribute()
|
|
{
|
|
// Arrange
|
|
var assembly = Assembly.GetExecutingAssembly();
|
|
|
|
// Act
|
|
var types = ReflectionHelper.FindTypesWithAttribute<TestAttribute>(assembly);
|
|
|
|
// Assert
|
|
Assert.IsTrue(types.Count > 0);
|
|
Assert.IsTrue(types.Any(t => t == typeof(User)));
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region 属性访问测试
|
|
|
|
[TestMethod]
|
|
public void GetPropertyValue_ReturnsCorrectValue()
|
|
{
|
|
// Arrange
|
|
var user = new User { Name = "Alice", Age = 25 };
|
|
|
|
// Act
|
|
var name = ReflectionHelper.GetPropertyValue(user, nameof(User.Name));
|
|
var age = ReflectionHelper.GetPropertyValue(user, nameof(User.Age));
|
|
|
|
// Assert
|
|
Assert.AreEqual("Alice", name);
|
|
Assert.AreEqual(25, age);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void SetPropertyValue_SetsValueCorrectly()
|
|
{
|
|
// Arrange
|
|
var user = new User();
|
|
|
|
// Act
|
|
ReflectionHelper.SetPropertyValue(user, nameof(User.Name), "Bob");
|
|
ReflectionHelper.SetPropertyValue(user, nameof(User.Age), 30);
|
|
|
|
// Assert
|
|
Assert.AreEqual("Bob", user.Name);
|
|
Assert.AreEqual(30, user.Age);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void GetPropertyValue_Performance_IsFasterThanDirectReflection()
|
|
{
|
|
// Arrange
|
|
var user = new User { Name = "Alice", Age = 25 };
|
|
const int iterations = 10000;
|
|
|
|
// 预热缓存
|
|
ReflectionHelper.GetPropertyValue(user, nameof(User.Name));
|
|
|
|
// Act: 使用 ReflectionHelper(有缓存)
|
|
var sw1 = System.Diagnostics.Stopwatch.StartNew();
|
|
for (var i = 0; i < iterations; i++) _ = ReflectionHelper.GetPropertyValue(user, nameof(User.Name));
|
|
sw1.Stop();
|
|
|
|
// Act: 使用原生反射(无缓存)
|
|
var propertyInfo = typeof(User).GetProperty(nameof(User.Name))!;
|
|
var sw2 = System.Diagnostics.Stopwatch.StartNew();
|
|
for (var i = 0; i < iterations; i++) _ = propertyInfo.GetValue(user);
|
|
sw2.Stop();
|
|
|
|
// Assert: ReflectionHelper 应该比直接反射快(由于缓存和表达式树)
|
|
Console.WriteLine($"ReflectionHelper: {sw1.ElapsedMilliseconds}ms");
|
|
Console.WriteLine($"Direct Reflection: {sw2.ElapsedMilliseconds}ms");
|
|
// 注意:首次运行可能因为 JIT 编译而不准确,多次运行后 ReflectionHelper 应该更快
|
|
}
|
|
|
|
[TestMethod]
|
|
public void GetPropertyNames_ReturnsAllPropertyNames()
|
|
{
|
|
// Act
|
|
var propertyNames = ReflectionHelper.GetPropertyNames<User>();
|
|
|
|
// Assert
|
|
Assert.IsTrue(propertyNames.Count >= 5);
|
|
CollectionAssert.Contains(propertyNames, nameof(User.Name));
|
|
CollectionAssert.Contains(propertyNames, nameof(User.Age));
|
|
CollectionAssert.Contains(propertyNames, nameof(User.Email));
|
|
}
|
|
|
|
[TestMethod]
|
|
public void GetProperties_ReturnsAllProperties()
|
|
{
|
|
// Act
|
|
var properties = ReflectionHelper.GetProperties<User>();
|
|
|
|
// Assert
|
|
Assert.IsTrue(properties.Length >= 5);
|
|
Assert.IsTrue(properties.Any(p => p.Name == nameof(User.Name)));
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region 特性读取测试
|
|
|
|
[TestMethod]
|
|
public void GetAttribute_ReturnsAttribute()
|
|
{
|
|
// Act
|
|
var attribute = ReflectionHelper.GetAttribute<TestAttribute>(typeof(User));
|
|
|
|
// Assert
|
|
Assert.IsNotNull(attribute);
|
|
Assert.AreEqual("UserEntity", attribute.Name);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void GetAttribute_FromProperty_ReturnsAttribute()
|
|
{
|
|
// Arrange
|
|
var property = typeof(User).GetProperty(nameof(User.Name))!;
|
|
|
|
// Act
|
|
var testAttr = ReflectionHelper.GetAttribute<TestAttribute>(property);
|
|
var descAttr = ReflectionHelper.GetAttribute<System.ComponentModel.DescriptionAttribute>(property);
|
|
|
|
// Assert
|
|
Assert.IsNotNull(testAttr);
|
|
Assert.AreEqual("UserName", testAttr.Name);
|
|
Assert.IsNotNull(descAttr);
|
|
Assert.AreEqual("用户名", descAttr.Description);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void HasAttribute_ChecksAttributeExistence()
|
|
{
|
|
// Act & Assert
|
|
Assert.IsTrue(ReflectionHelper.HasAttribute<TestAttribute>(typeof(User)));
|
|
Assert.IsFalse(ReflectionHelper.HasAttribute<TestAttribute>(typeof(UserDto)));
|
|
}
|
|
|
|
[TestMethod]
|
|
public void GetAttribute_UsesCaching()
|
|
{
|
|
// Arrange
|
|
var type = typeof(User);
|
|
|
|
// Act: 多次获取应该使用缓存
|
|
var attr1 = ReflectionHelper.GetAttribute<TestAttribute>(type);
|
|
var attr2 = ReflectionHelper.GetAttribute<TestAttribute>(type);
|
|
|
|
// Assert: 应该返回相同的实例(来自缓存)
|
|
Assert.IsNotNull(attr1);
|
|
Assert.IsNotNull(attr2);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region 创建实例测试
|
|
|
|
[TestMethod]
|
|
public void CreateInstance_Generic_CreatesInstance()
|
|
{
|
|
// Act
|
|
var user = ReflectionHelper.CreateInstance<User>();
|
|
|
|
// Assert
|
|
Assert.IsNotNull(user);
|
|
Assert.IsInstanceOfType(user, typeof(User));
|
|
}
|
|
|
|
[TestMethod]
|
|
public void CreateInstance_ByType_CreatesInstance()
|
|
{
|
|
// Act
|
|
var user = ReflectionHelper.CreateInstance(typeof(User));
|
|
|
|
// Assert
|
|
Assert.IsNotNull(user);
|
|
Assert.IsInstanceOfType(user, typeof(User));
|
|
}
|
|
|
|
[TestMethod]
|
|
public void TryCreateInstance_SuccessfulCreation_ReturnsTrue()
|
|
{
|
|
// Act
|
|
var success = ReflectionHelper.TryCreateInstance<User>(out var user);
|
|
|
|
// Assert
|
|
Assert.IsTrue(success);
|
|
Assert.IsNotNull(user);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region 属性复制测试
|
|
|
|
[TestMethod]
|
|
public void CopyProperties_CopiesSameNameProperties()
|
|
{
|
|
// Arrange
|
|
var source = new User
|
|
{
|
|
Name = "Alice",
|
|
Age = 25,
|
|
Email = "alice@example.com"
|
|
};
|
|
var target = new UserDto();
|
|
|
|
// Act
|
|
ReflectionHelper.CopyProperties(source, target);
|
|
|
|
// Assert
|
|
Assert.AreEqual("Alice", target.Name);
|
|
Assert.AreEqual(25, target.Age);
|
|
Assert.AreEqual("alice@example.com", target.Email);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void MapTo_CreatesNewInstanceAndCopiesProperties()
|
|
{
|
|
// Arrange
|
|
var source = new User
|
|
{
|
|
Name = "Bob",
|
|
Age = 30,
|
|
Email = "bob@example.com"
|
|
};
|
|
|
|
// Act
|
|
var dto = ReflectionHelper.MapTo<User, UserDto>(source);
|
|
|
|
// Assert
|
|
Assert.IsNotNull(dto);
|
|
Assert.AreEqual("Bob", dto.Name);
|
|
Assert.AreEqual(30, dto.Age);
|
|
Assert.AreEqual("bob@example.com", dto.Email);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void CopyProperties_IgnoreCase_CopiesCorrectly()
|
|
{
|
|
// Arrange
|
|
var source = new { name = "Alice", AGE = 25 };
|
|
var target = new UserDto();
|
|
|
|
// Act
|
|
ReflectionHelper.CopyProperties(source, target, true);
|
|
|
|
// Assert
|
|
Assert.AreEqual("Alice", target.Name);
|
|
Assert.AreEqual(25, target.Age);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region 方法调用测试
|
|
|
|
[TestMethod]
|
|
public void InvokeMethod_CallsInstanceMethod()
|
|
{
|
|
// Arrange
|
|
var user = new User { Name = "Alice" };
|
|
|
|
// Act
|
|
var result = ReflectionHelper.InvokeMethod(user, nameof(User.Calculate), 10, 20);
|
|
|
|
// Assert
|
|
Assert.IsNotNull(result);
|
|
Assert.AreEqual(30, result);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void InvokeStaticMethod_CallsStaticMethod()
|
|
{
|
|
// Act
|
|
var result = ReflectionHelper.InvokeStaticMethod(typeof(User), nameof(User.GetVersion));
|
|
|
|
// Assert
|
|
Assert.IsNotNull(result);
|
|
Assert.AreEqual("1.0.0", result);
|
|
}
|
|
|
|
[TestMethod]
|
|
[ExpectedException(typeof(InvalidOperationException))]
|
|
public void InvokeMethod_NonExistentMethod_ThrowsException()
|
|
{
|
|
// Arrange
|
|
var user = new User();
|
|
|
|
// Act
|
|
ReflectionHelper.InvokeMethod(user, "NonExistentMethod");
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region 类型判断测试
|
|
|
|
[TestMethod]
|
|
public void IsNullableType_DetectsNullableTypes()
|
|
{
|
|
// Act & Assert
|
|
Assert.IsTrue(ReflectionHelper.IsNullableType(typeof(int?)));
|
|
Assert.IsFalse(ReflectionHelper.IsNullableType(typeof(int)));
|
|
Assert.IsFalse(ReflectionHelper.IsNullableType(typeof(string)));
|
|
}
|
|
|
|
[TestMethod]
|
|
public void GetUnderlyingType_ReturnsCorrectType()
|
|
{
|
|
// Act
|
|
var underlyingType = ReflectionHelper.GetUnderlyingType(typeof(int?));
|
|
|
|
// Assert
|
|
Assert.AreEqual(typeof(int), underlyingType);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void IsValueType_DistinguishesValueTypes()
|
|
{
|
|
// Act & Assert
|
|
Assert.IsTrue(ReflectionHelper.IsValueType(typeof(int)));
|
|
Assert.IsTrue(ReflectionHelper.IsValueType(typeof(DateTime)));
|
|
Assert.IsFalse(ReflectionHelper.IsValueType(typeof(string)));
|
|
Assert.IsFalse(ReflectionHelper.IsValueType(typeof(User)));
|
|
}
|
|
|
|
[TestMethod]
|
|
public void IsNumericType_DetectsNumericTypes()
|
|
{
|
|
// Act & Assert
|
|
Assert.IsTrue(ReflectionHelper.IsNumericType(typeof(int)));
|
|
Assert.IsTrue(ReflectionHelper.IsNumericType(typeof(double)));
|
|
Assert.IsTrue(ReflectionHelper.IsNumericType(typeof(decimal)));
|
|
Assert.IsTrue(ReflectionHelper.IsNumericType(typeof(int?)));
|
|
Assert.IsFalse(ReflectionHelper.IsNumericType(typeof(string)));
|
|
Assert.IsFalse(ReflectionHelper.IsNumericType(typeof(DateTime)));
|
|
}
|
|
|
|
[TestMethod]
|
|
public void IsCollectionType_DetectsCollections()
|
|
{
|
|
// Act & Assert
|
|
Assert.IsTrue(ReflectionHelper.IsCollectionType(typeof(int[])));
|
|
Assert.IsTrue(ReflectionHelper.IsCollectionType(typeof(List<int>)));
|
|
Assert.IsTrue(ReflectionHelper.IsCollectionType(typeof(IEnumerable<int>)));
|
|
Assert.IsFalse(ReflectionHelper.IsCollectionType(typeof(int)));
|
|
Assert.IsFalse(ReflectionHelper.IsCollectionType(typeof(string)));
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region 泛型操作测试
|
|
|
|
[TestMethod]
|
|
public void IsGenericType_DetectsGenericTypes()
|
|
{
|
|
// Act & Assert
|
|
Assert.IsTrue(ReflectionHelper.IsGenericType(typeof(List<int>)));
|
|
Assert.IsTrue(ReflectionHelper.IsGenericType(typeof(Dictionary<string, int>)));
|
|
Assert.IsFalse(ReflectionHelper.IsGenericType(typeof(int)));
|
|
Assert.IsFalse(ReflectionHelper.IsGenericType(typeof(string)));
|
|
}
|
|
|
|
[TestMethod]
|
|
public void GetGenericArguments_ReturnsTypeArguments()
|
|
{
|
|
// Act
|
|
var args = ReflectionHelper.GetGenericArguments(typeof(Dictionary<string, int>));
|
|
|
|
// Assert
|
|
Assert.AreEqual(2, args.Length);
|
|
Assert.AreEqual(typeof(string), args[0]);
|
|
Assert.AreEqual(typeof(int), args[1]);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void MakeGenericType_ConstructsGenericType()
|
|
{
|
|
// Act
|
|
var listType = ReflectionHelper.MakeGenericType(typeof(List<>), typeof(string));
|
|
|
|
// Assert
|
|
Assert.AreEqual(typeof(List<string>), listType);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region 缓存管理测试
|
|
|
|
[TestMethod]
|
|
public void ClearCache_ClearsAllCaches()
|
|
{
|
|
// Arrange: 先访问一些缓存
|
|
ReflectionHelper.GetPropertyValue(new User { Name = "Test" }, nameof(User.Name));
|
|
ReflectionHelper.GetAttribute<TestAttribute>(typeof(User));
|
|
|
|
// Act
|
|
ReflectionHelper.ClearCache();
|
|
|
|
// Assert: 缓存应该被清空(无法直接验证,但不应抛异常)
|
|
Assert.IsTrue(true);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void ClearPropertyAccessorCache_ClearsPropertyCache()
|
|
{
|
|
// Arrange
|
|
ReflectionHelper.GetPropertyValue(new User { Name = "Test" }, nameof(User.Name));
|
|
|
|
// Act
|
|
ReflectionHelper.ClearPropertyAccessorCache();
|
|
|
|
// Assert
|
|
Assert.IsTrue(true);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region 实际应用场景测试
|
|
|
|
[TestMethod]
|
|
public void Scenario_PluginScanning()
|
|
{
|
|
// Arrange: 模拟插件系统,扫描所有实现 ITestService 的类
|
|
var assembly = Assembly.GetExecutingAssembly();
|
|
|
|
// Act: 查找所有插件
|
|
var plugins = ReflectionHelper.FindImplementationsOf<ITestService>(assembly);
|
|
|
|
// Assert
|
|
Assert.IsTrue(plugins.Count > 0);
|
|
|
|
// Act: 实例化并执行插件
|
|
foreach (var pluginType in plugins)
|
|
if (ReflectionHelper.CreateInstance(pluginType) is ITestService plugin)
|
|
plugin.Execute(); // 输出:Executing test service
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Scenario_AutoMapper()
|
|
{
|
|
// Arrange: 模拟 DTO 映射
|
|
var user = new User
|
|
{
|
|
Id = 1,
|
|
Name = "Alice",
|
|
Age = 25,
|
|
Email = "alice@example.com",
|
|
CreatedAt = DateTime.Now
|
|
};
|
|
|
|
// Act: 自动映射到 DTO
|
|
var dto = ReflectionHelper.MapTo<User, UserDto>(user);
|
|
|
|
// Assert
|
|
Assert.IsNotNull(dto);
|
|
Assert.AreEqual("Alice", dto.Name);
|
|
Assert.AreEqual(25, dto.Age);
|
|
Assert.AreEqual("alice@example.com", dto.Email);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Scenario_DynamicPropertyAccess()
|
|
{
|
|
// Arrange: 模拟动态表单数据
|
|
var user = new User { Name = "Bob", Age = 30 };
|
|
var propertyUpdates = new Dictionary<string, object>
|
|
{
|
|
{ nameof(User.Name), "Updated Bob" },
|
|
{ nameof(User.Age), 31 },
|
|
{ nameof(User.Email), "bob@updated.com" }
|
|
};
|
|
|
|
// Act: 动态更新属性
|
|
foreach (var (propertyName, value) in propertyUpdates)
|
|
ReflectionHelper.SetPropertyValue(user, propertyName, value);
|
|
|
|
// Assert
|
|
Assert.AreEqual("Updated Bob", user.Name);
|
|
Assert.AreEqual(31, user.Age);
|
|
Assert.AreEqual("bob@updated.com", user.Email);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Scenario_AttributeBasedValidation()
|
|
{
|
|
// Arrange: 读取属性上的描述特性用于验证消息
|
|
var properties = typeof(User).GetProperties();
|
|
var validationMessages = new Dictionary<string, string>();
|
|
|
|
// Act: 收集所有带描述的属性
|
|
foreach (var property in properties)
|
|
{
|
|
var description = ReflectionHelper.GetAttribute<System.ComponentModel.DescriptionAttribute>(property);
|
|
if (description != null) validationMessages[property.Name] = $"{description.Description}不能为空";
|
|
}
|
|
|
|
// Assert
|
|
Assert.IsTrue(validationMessages.ContainsKey(nameof(User.Name)));
|
|
Assert.AreEqual("用户名不能为空", validationMessages[nameof(User.Name)]);
|
|
Assert.IsTrue(validationMessages.ContainsKey(nameof(User.Age)));
|
|
Assert.AreEqual("用户年龄不能为空", validationMessages[nameof(User.Age)]);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Scenario_TypeInspection()
|
|
{
|
|
// Arrange: 检查类型信息
|
|
var type = typeof(User);
|
|
|
|
// Act: 收集类型信息
|
|
var info = new
|
|
{
|
|
IsClass = type.IsClass,
|
|
IsAbstract = type.IsAbstract,
|
|
BaseType = type.BaseType?.Name,
|
|
HasTestAttribute = ReflectionHelper.HasAttribute<TestAttribute>(type),
|
|
PropertyCount = ReflectionHelper.GetProperties<User>().Length,
|
|
IsValueType = ReflectionHelper.IsValueType(type)
|
|
};
|
|
|
|
// Assert
|
|
Assert.IsTrue(info.IsClass);
|
|
Assert.IsFalse(info.IsAbstract);
|
|
Assert.AreEqual("BaseEntity", info.BaseType);
|
|
Assert.IsTrue(info.HasTestAttribute);
|
|
Assert.IsTrue(info.PropertyCount >= 5);
|
|
Assert.IsFalse(info.IsValueType);
|
|
}
|
|
|
|
#endregion
|
|
} |