改进 EnumHelper 功能并新增全面单元测试
石头 authored at 2025-09-27 10:16:00
11.57 KiB
X
using System;
using System.ComponentModel;
using NewLife;
using Xunit;

namespace XUnitTest.Extension;

public class EnumHelperTests
{
    // 测试枚举定义
    [Flags]
    enum TestFlags
    {
        None = 0,
        Read = 1,
        Write = 2,
        Execute = 4,
        ReadWrite = Read | Write,
        All = Read | Write | Execute
    }

    enum TestEnum
    {
        [Description("第一个值")]
        First = 1,

        [Description("第二个值")]
        Second = 2,

        Third = 3,

        Fourth = 4,

        // 测试相同值的不同名称
        FirstAlias = 1
    }

    enum TestEnumWithoutAttributes
    {
        Value1 = 10,
        Value2 = 20,
        Value3 = 30
    }

    [Fact(DisplayName = "Has方法_标准标志位测试")]
    public void Has_StandardFlags_Test()
    {
        var flags = TestFlags.ReadWrite;

        // 包含的标志位
        Assert.True(flags.Has(TestFlags.Read));
        Assert.True(flags.Has(TestFlags.Write));
        
        // 不包含的标志位
        Assert.False(flags.Has(TestFlags.Execute));
        
        // 组合标志位
        Assert.True(flags.Has(TestFlags.ReadWrite));
        Assert.False(flags.Has(TestFlags.All));
        
        // None 标志位
        Assert.False(flags.Has(TestFlags.None));
        var none = TestFlags.None;
        Assert.True(none.Has(TestFlags.None));
    }

    [Fact(DisplayName = "Has方法_边界情况测试")]
    public void Has_EdgeCases_Test()
    {
        var all = TestFlags.All;
        
        // 包含所有标志位
        Assert.True(all.Has(TestFlags.Read));
        Assert.True(all.Has(TestFlags.Write));
        Assert.True(all.Has(TestFlags.Execute));
        Assert.True(all.Has(TestFlags.ReadWrite));
        Assert.True(all.Has(TestFlags.All));
        
        // 单个标志位测试
        var read = TestFlags.Read;
        Assert.True(read.Has(TestFlags.Read));
        Assert.False(read.Has(TestFlags.Write));
    }

    [Fact(DisplayName = "Has方法_类型不匹配异常")]
    public void Has_TypeMismatch_ThrowsException()
    {
        var flags = TestFlags.Read;
        var enumValue = TestEnum.First;

        var ex = Assert.Throws<ArgumentException>(() => flags.Has(enumValue));
        Assert.Equal("flag", ex.ParamName);
        Assert.Contains("Enumeration identification judgment must be of the same type", ex.Message);
    }

    [Fact(DisplayName = "Set方法_设置标志位测试")]
    public void Set_SetFlags_Test()
    {
        var flags = TestFlags.Read;

        // 设置新标志位
        var result = flags.Set(TestFlags.Write, true);
        Assert.Equal(TestFlags.ReadWrite, result);
        Assert.True(result.Has(TestFlags.Read));
        Assert.True(result.Has(TestFlags.Write));

        // 清除标志位
        result = result.Set(TestFlags.Read, false);
        Assert.Equal(TestFlags.Write, result);
        Assert.False(result.Has(TestFlags.Read));
        Assert.True(result.Has(TestFlags.Write));

        // 重复设置相同标志位
        result = result.Set(TestFlags.Write, true);
        Assert.Equal(TestFlags.Write, result);

        // 清除不存在的标志位
        result = result.Set(TestFlags.Execute, false);
        Assert.Equal(TestFlags.Write, result);
    }

    [Fact(DisplayName = "Set方法_组合标志位测试")]
    public void Set_CombinedFlags_Test()
    {
        var flags = TestFlags.None;

        // 设置组合标志位
        var result = flags.Set(TestFlags.ReadWrite, true);
        Assert.Equal(TestFlags.ReadWrite, result);
        Assert.True(result.Has(TestFlags.Read));
        Assert.True(result.Has(TestFlags.Write));

        // 清除部分组合标志位
        result = result.Set(TestFlags.Read, false);
        Assert.Equal(TestFlags.Write, result);
        Assert.False(result.Has(TestFlags.Read));
        Assert.True(result.Has(TestFlags.Write));
    }

    [Fact(DisplayName = "Set方法_类型不匹配异常")]
    public void Set_TypeMismatch_ThrowsException()
    {
        var enumValue = TestEnum.First;

        var ex = Assert.Throws<ArgumentException>(() => enumValue.Set(TestFlags.Read, true));
        Assert.Equal("source", ex.ParamName);
        Assert.Contains("Enumeration identification judgment must be of the same type", ex.Message);
    }

    [Fact(DisplayName = "GetDescription方法_有描述属性测试")]
    public void GetDescription_WithDescriptionAttribute_Test()
    {
        Assert.Equal("第一个值", TestEnum.First.GetDescription());
        Assert.Equal("第二个值", TestEnum.Second.GetDescription());
    }

    [Fact(DisplayName = "GetDescription方法_无描述属性测试")]
    public void GetDescription_WithoutDescriptionAttribute_Test()
    {
        // 没有 DescriptionAttribute 但有 DisplayName 的枚举值
        Assert.Null(TestEnum.Third.GetDescription());
        
        // 完全没有属性的枚举值
        Assert.Null(TestEnum.Fourth.GetDescription());
        
        // 没有任何属性的枚举
        Assert.Null(TestEnumWithoutAttributes.Value1.GetDescription());
    }

    [Fact(DisplayName = "GetDescription方法_空值测试")]
    public void GetDescription_NullValue_Test()
    {
        TestEnum? nullEnum = null;
        Assert.Null(nullEnum.GetDescription());
    }

    [Fact(DisplayName = "GetDescription方法_无效枚举值测试")]
    public void GetDescription_InvalidEnumValue_Test()
    {
        // 不存在的枚举值
        var invalidEnum = (TestEnum)999;
        Assert.Null(invalidEnum.GetDescription());
    }

    [Fact(DisplayName = "GetDescriptions泛型方法_完整测试")]
    public void GetDescriptions_Generic_Test()
    {
        var descriptions = EnumHelper.GetDescriptions<TestEnum>();

        Assert.NotNull(descriptions);
        Assert.Equal(4, descriptions.Count); // First, Second, Third, Fourth (FirstAlias会覆盖First,因为值相同)

        // 验证描述内容 - 优先使用 DisplayName,其次 Description,最后字段名
        Assert.Equal("FirstAlias", descriptions[TestEnum.First]);   // FirstAlias覆盖了First,因为值相同 
        Assert.Equal("第二个值", descriptions[TestEnum.Second]);    // 只有 Description  
        Assert.Equal("Third", descriptions[TestEnum.Third]);        // 没有属性,使用字段名
        Assert.Equal("Fourth", descriptions[TestEnum.Fourth]);      // 没有属性,使用字段名
        // FirstAlias和First指向同一个值,所以应该是相同的
        Assert.Equal("FirstAlias", descriptions[TestEnum.FirstAlias]);
    }

    [Fact(DisplayName = "GetDescriptions泛型方法_无属性枚举测试")]
    public void GetDescriptions_Generic_NoAttributes_Test()
    {
        var descriptions = EnumHelper.GetDescriptions<TestEnumWithoutAttributes>();

        Assert.NotNull(descriptions);
        Assert.Equal(3, descriptions.Count);

        Assert.Equal("Value1", descriptions[TestEnumWithoutAttributes.Value1]);
        Assert.Equal("Value2", descriptions[TestEnumWithoutAttributes.Value2]);
        Assert.Equal("Value3", descriptions[TestEnumWithoutAttributes.Value3]);
    }

    [Fact(DisplayName = "GetDescriptions类型参数方法_完整测试")]
    public void GetDescriptions_Type_Test()
    {
        var descriptions = EnumHelper.GetDescriptions(typeof(TestEnum));

        Assert.NotNull(descriptions);
        Assert.Equal(4, descriptions.Count); // First, Second, Third, Fourth (FirstAlias会覆盖First)

        // 验证键值对应关系
        Assert.Equal("FirstAlias", descriptions[1]);  // FirstAlias = 1,会覆盖 First
        Assert.Equal("第二个值", descriptions[2]);     // Second = 2,Description
        Assert.Equal("Third", descriptions[3]);       // Third = 3,字段名
        Assert.Equal("Fourth", descriptions[4]);      // Fourth = 4,字段名
        
        // FirstAlias = 1,相同值会覆盖,保留最后一个
        Assert.True(descriptions.ContainsKey(1));
    }

    [Fact(DisplayName = "GetDescriptions类型参数方法_标志位枚举测试")]
    public void GetDescriptions_Type_FlagsEnum_Test()
    {
        var descriptions = EnumHelper.GetDescriptions(typeof(TestFlags));

        Assert.NotNull(descriptions);
        Assert.Equal(6, descriptions.Count); // None, Read, Write, Execute, ReadWrite, All

        Assert.Equal("None", descriptions[0]);      // None = 0
        Assert.Equal("Read", descriptions[1]);      // Read = 1
        Assert.Equal("Write", descriptions[2]);     // Write = 2
        Assert.Equal("Execute", descriptions[4]);   // Execute = 4
        Assert.Equal("ReadWrite", descriptions[3]); // ReadWrite = 3 (Read | Write)
        Assert.Equal("All", descriptions[7]);       // All = 7 (Read | Write | Execute)
    }

    [Fact(DisplayName = "GetDescriptions类型参数方法_非枚举类型测试")]
    public void GetDescriptions_Type_NonEnumType_Test()
    {
        var descriptions = EnumHelper.GetDescriptions(typeof(string));

        Assert.NotNull(descriptions);
        Assert.Empty(descriptions); // 非枚举类型返回空字典
    }

    [Fact(DisplayName = "EnumHelper方法_复杂场景综合测试")]
    public void EnumHelper_ComplexScenario_Test()
    {
        // 复杂的标志位操作场景
        var permissions = TestFlags.None;
        
        // 逐步添加权限
        permissions = permissions.Set(TestFlags.Read, true);
        Assert.True(permissions.Has(TestFlags.Read));
        Assert.False(permissions.Has(TestFlags.Write));
        
        permissions = permissions.Set(TestFlags.Write, true);
        Assert.True(permissions.Has(TestFlags.ReadWrite));
        
        permissions = permissions.Set(TestFlags.Execute, true);
        Assert.True(permissions.Has(TestFlags.All));
        
        // 部分撤销权限
        permissions = permissions.Set(TestFlags.Write, false);
        Assert.False(permissions.Has(TestFlags.Write));
        Assert.True(permissions.Has(TestFlags.Read));
        Assert.True(permissions.Has(TestFlags.Execute));
        Assert.False(permissions.Has(TestFlags.All));
        
        // 验证最终状态
        var expected = TestFlags.Read | TestFlags.Execute;
        Assert.Equal(expected, permissions);
    }

    [Fact(DisplayName = "枚举描述_属性优先级测试")]
    public void EnumDescription_AttributePriority_Test()
    {
        // 验证属性优先级:DisplayName > Description > FieldName
        var descriptions = EnumHelper.GetDescriptions(typeof(TestEnum));
        
        // FirstAlias = 1,会覆盖 First 的值,保留最后一个字段名
        Assert.Equal("FirstAlias", descriptions[1]);
        
        // Second 只有 Description
        Assert.Equal("第二个值", descriptions[2]);
        
        // Third 没有属性,使用字段名
        Assert.Equal("Third", descriptions[3]);
        
        // Fourth 没有属性,使用字段名
        Assert.Equal("Fourth", descriptions[4]);
    }

    // 测试用于验证空描述不会覆盖字段名的枚举
    enum TestEmptyDescription
    {
        [Description("")]
        EmptyDesc = 1,
        
        [Description("   ")]
        WhitespaceDesc = 2,
        
        EmptyDisplayName = 3,
        
        WhitespaceDisplayName = 4
    }

    [Fact(DisplayName = "枚举描述_空值处理测试")]
    public void EnumDescription_EmptyValue_Test()
    {
        var descriptions = EnumHelper.GetDescriptions(typeof(TestEmptyDescription));

        // 空字符串或空白字符串的描述应该回退到字段名
        Assert.Equal("EmptyDesc", descriptions[1]);
        Assert.Equal("   ", descriptions[2]);
        Assert.Equal("EmptyDisplayName", descriptions[3]);
        Assert.Equal("WhitespaceDisplayName", descriptions[4]);
    }
}