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

如何让ExpectedObjects仅按公共接口对比对象?

解决ExpectedObjects仅按公共接口对比对象的问题

首先,我完全理解你的痛点:ExpectedObjects默认会对比对象的所有公共属性,不管它实现的接口,所以混合了SimpleEntity和ComplexEntity的结果集,会因为额外属性(比如ComplexEntity的Domain、非零的Id)不匹配导致断言失败;而手动投影到匿名类型的方式,又会在接口新增属性时无法自动验证,埋下漏测的隐患。

一、开箱即用的半自动化方案:自定义属性投影扩展

虽然ExpectedObjects没有直接提供“按接口对比”的内置功能,但我们可以写一个通用的扩展方法,动态提取接口的所有公共属性,把对象投影成仅包含这些属性的结构。这样后续接口新增属性时,测试代码不需要任何修改,就能自动验证新属性。

步骤1:编写通用投影扩展方法

这个方法会反射获取接口的所有公共实例属性,然后把每个对象转换为包含这些属性的ExpandoObject(ExpectedObjects可以正常识别这种动态结构):

using System.Dynamic;
using System.Reflection;

public static class InterfaceProjectionExtensions
{
    public static IEnumerable<object> ProjectToInterface<TInterface>(this IEnumerable<TInterface> items) 
        where TInterface : class
    {
        var interfaceType = typeof(TInterface);
        var properties = interfaceType.GetProperties(BindingFlags.Public | BindingFlags.Instance);

        foreach (var item in items)
        {
            if (item == null)
            {
                yield return null;
                continue;
            }

            var expando = new ExpandoObject() as IDictionary<string, object>;
            foreach (var prop in properties)
            {
                expando[prop.Name] = prop.GetValue(item);
            }
            yield return expando;
        }
    }
}

步骤2:修改测试断言

现在只需要把预期集和实际结果都通过这个扩展方法投影,再用ExpectedObjects对比:

// ARRANGE
IEnumerable<INamed> expected = new [] 
{ 
    new SimpleEntity { Name = "NAM1" }, 
    new SimpleEntity { Name = "NAM2" } 
};

// ACT
var result = systemUnderTest.GetNames();

// ASSERT
expected.ProjectToInterface<INamed>().ToExpectedObject().ShouldMatch(result.ProjectToInterface<INamed>());

这样做的好处:

  • 仅对比INamed接口定义的所有属性,忽略实现类的额外属性
  • 后续给INamed新增属性(比如Id)时,扩展方法会自动提取新属性,测试会自动验证,不会遗漏

二、进阶方案:自定义ExpectedObjects比较器

如果你希望直接使用ExpectedObjects的链式API,而不是投影,可以自定义一个比较器,强制仅对比指定接口的属性:

步骤1:实现接口属性比较器

using System.Reflection;

public class InterfaceEqualityComparer<TInterface> : IEqualityComparer
{
    public new bool Equals(object x, object y)
    {
        if (ReferenceEquals(x, y)) return true;
        if (x == null || y == null) return false;

        // 确保两个对象都实现了目标接口
        if (!typeof(TInterface).IsAssignableFrom(x.GetType()) || 
            !typeof(TInterface).IsAssignableFrom(y.GetType()))
        {
            return false;
        }

        var interfaceProperties = typeof(TInterface).GetProperties(BindingFlags.Public | BindingFlags.Instance);
        foreach (var prop in interfaceProperties)
        {
            var xValue = prop.GetValue(x);
            var yValue = prop.GetValue(y);
            
            // 处理值类型和引用类型的相等性
            if (!object.Equals(xValue, yValue))
            {
                return false;
            }
        }

        return true;
    }

    public int GetHashCode(object obj)
    {
        // 基于接口属性生成哈希码(可选,用于集合比较的性能优化)
        var interfaceProperties = typeof(TInterface).GetProperties(BindingFlags.Public | BindingFlags.Instance);
        int hash = 17;
        foreach (var prop in interfaceProperties)
        {
            var value = prop.GetValue(obj);
            hash = hash * 31 + (value?.GetHashCode() ?? 0);
        }
        return hash;
    }
}

步骤2:在测试中使用自定义比较器

// ARRANGE
IEnumerable<INamed> expected = new [] 
{ 
    new SimpleEntity { Name = "NAM1" }, 
    new SimpleEntity { Name = "NAM2" } 
};

// ACT
var result = systemUnderTest.GetNames();

// ASSERT
expected.ToExpectedObject()
    .WithCustomComparer(new InterfaceEqualityComparer<INamed>())
    .ShouldMatch(result);

这个方案更贴近ExpectedObjects的原生用法,比较逻辑完全封装在比较器中,测试代码更简洁。

三、临时过渡方案:使用接口专属Stub类

如果不想写反射代码,可以创建一个仅实现INamed接口的Stub类,用它来构造预期集:

public class NamedStub : INamed
{
    public string Name { get; set; }
    // 后续INamed新增属性时,同步添加到这里
}

测试时用Stub构造预期:

IEnumerable<INamed> expected = new [] 
{ 
    new NamedStub { Name = "NAM1" }, 
    new NamedStub { Name = "NAM2" } 
};

expected.ToExpectedObject().ShouldMatch(result);

这个方案缺点是需要手动维护Stub类和接口的一致性,但优点是实现简单,适合接口变更不频繁的场景。


内容的提问来源于stack exchange,提问作者V0ldek

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.14 06:53:18