You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

NUnit中List<T>的.Is.EquivalentTo断言不符合预期问题排查

问题:自定义对象集合的NUnit等价断言失败,字符串列表集合正常

现象说明

在.NET 4.5.2项目中使用NUnit框架的Assert.That(first, Is.EquivalentTo(second))进行集合无序等价断言时:

  • List<List<string>>类型的两个集合测试通过
  • List<SimpleObject>类型的属性值完全一致的集合断言失败

通过的测试代码

[Test] 
public void Test_Int_ExampleDeepListCompare()
{
    List<List<string>> first = new List<List<string>>()
    {
        new List<string> { "1", "3" },
        new List<string> { "1", "2" },
        new List<string> { "a", "b" }
    };

    List<List<string>> second = new List<List<string>>()
    {
        new List<string> { "1", "2" },
        new List<string> { "1", "3" },
        new List<string> { "a", "b" }
    };
    
    Assert.That(first, Is.EquivalentTo(second));
}

失败的测试代码

[Test]
public void Test_Int_ExampleDeepCompareCustomObjects2()
{
    List<SimpleObject> rtnValFakeA = new List<SimpleObject>() { new SimpleObject() { FirstName = "Bob", LastName = "Jones", Mi = "a", StudId = 12345 } };
    List<SimpleObject> rtnValFakeb = new List<SimpleObject>() { new SimpleObject() { FirstName = "Bob", LastName = "Jones", Mi = "a", StudId = 12345 } };

    //assert with deep compare ignoring order - EWB
    Assert.That(rtnValFakeA, Is.EquivalentTo(rtnValFakeb));
}

SimpleObject定义

public class SimpleObject
{
    public string LastName { get; set; }
    public string FirstName { get; set; }
    public string Mi { get; set; }
    public Int64 StudId { get; set; }
}

失败提示

Expected: equivalent to < <_Test_DAL.SimpleObject> >
But was: < <_Test_DAL.SimpleObject> >

原因分析

  1. 内置类型的相等性实现:List<string>是.NET内置集合,它重写了Equals和GetHashCode方法,会深度比较集合内的所有元素是否相等,因此NUnit能正确识别两个List<string>实例的等价性。
  2. 自定义类型的默认相等性:自定义的SimpleObject类默认使用引用相等(即比较对象的内存地址),即使两个实例的属性值完全相同,它们也是不同的对象实例,NUnit会判定这两个对象不等价,最终导致集合断言失败。
  3. Is.EquivalentTo的工作逻辑:该断言用于判断两个集合是否包含相同的元素(不考虑顺序),但元素的相等性完全依赖于元素类型自身的Equals方法实现。

解决方案(适配.NET 4.5.2)

方案1:重写SimpleObject的Equals和GetHashCode方法

直接修改自定义类,让它基于属性值判断相等性:

public class SimpleObject
{
    public string LastName { get; set; }
    public string FirstName { get; set; }
    public string Mi { get; set; }
    public Int64 StudId { get; set; }

    public override bool Equals(object obj)
    {
        // 先判断类型是否匹配
        if (obj is not SimpleObject other)
            return false;
        
        // 比较所有属性值
        return LastName == other.LastName
               && FirstName == other.FirstName
               && Mi == other.Mi
               && StudId == other.StudId;
    }

    public override int GetHashCode()
    {
        // 计算哈希值,确保属性相同的对象哈希值一致
        unchecked
        {
            int hash = 17;
            hash = hash * 23 + (LastName?.GetHashCode() ?? 0);
            hash = hash * 23 + (FirstName?.GetHashCode() ?? 0);
            hash = hash * 23 + (Mi?.GetHashCode() ?? 0);
            hash = hash * 23 + StudId.GetHashCode();
            return hash;
        }
    }
}

修改后,原有的断言代码无需改动即可正常通过。

方案2:使用自定义相等比较器(不修改原类)

如果无法修改SimpleObject类,可以定义一个实现IEqualityComparer<SimpleObject>的比较器,在断言中指定使用该比较器:

定义比较器

public class SimpleObjectEqualityComparer : IEqualityComparer<SimpleObject>
{
    public bool Equals(SimpleObject x, SimpleObject y)
    {
        if (x == null && y == null) return true;
        if (x == null || y == null) return false;
        
        return x.LastName == y.LastName
               && x.FirstName == y.FirstName
               && x.Mi == y.Mi
               && x.StudId == y.StudId;
    }

    public int GetHashCode(SimpleObject obj)
    {
        if (obj == null) return 0;
        unchecked
        {
            int hash = 17;
            hash = hash * 23 + (obj.LastName?.GetHashCode() ?? 0);
            hash = hash * 23 + (obj.FirstName?.GetHashCode() ?? 0);
            hash = hash * 23 + (obj.Mi?.GetHashCode() ?? 0);
            hash = hash * 23 + obj.StudId.GetHashCode();
            return hash;
        }
    }
}

修改断言代码

Assert.That(rtnValFakeA, Is.EquivalentTo(rtnValFakeb).Using(new SimpleObjectEqualityComparer()));

方案3:使用属性匹配断言(临时场景)

如果只是单次测试需要验证,也可以直接通过属性匹配的方式逐个验证元素,无需修改类或定义比较器:

// 验证集合数量一致
Assert.That(rtnValFakeA.Count, Is.EqualTo(rtnValFakeb.Count));
// 验证每个元素都能在另一个集合中找到属性匹配的对象
Assert.That(rtnValFakeA, Has.All.Matches<SimpleObject>(item => 
    rtnValFakeb.Any(other => 
        item.FirstName == other.FirstName && 
        item.LastName == other.LastName && 
        item.Mi == other.Mi && 
        item.StudId == other.StudId)));

这种方式适合简单场景,但集合元素较多时代码会比较繁琐。


内容的提问来源于stack exchange,提问作者Eric Brown - Cal

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.27 11:17:58