如何在IEqualityComparer上实现单元测试?

Fre*_*ios 5 c# nunit unit-testing

我有一个类和一个比较器,该类实现IEqualityComparer

class Foo
{
    public int Int { get; set; }
    public string Str { get; set; }

    public Foo(int i, string s)
    {
        Int = i;
        Str = s;
    }

    private sealed class FooEqualityComparer : IEqualityComparer<Foo>
    {
        public bool Equals(Foo x, Foo y)
        {
            if (ReferenceEquals(x, y)) return true;
            if (ReferenceEquals(x, null)) return false;
            if (ReferenceEquals(y, null)) return false;
            if (x.GetType() != y.GetType()) return false;
            return x.Int == y.Int && string.Equals(x.Str, y.Str);
        }

        public int GetHashCode(Foo obj)
        {
            unchecked
            {
                return (obj.Int * 397) ^ (obj.Str != null ? obj.Str.GetHashCode() : 0);
            }
        }
    }

    public static IEqualityComparer<Foo> Comparer { get; } = new FooEqualityComparer();
}
Run Code Online (Sandbox Code Playgroud)

这两种方法Equals例如通过比较器的实例GetHashCode使用。List.Except

我的问题是:如何在此比较器上正确实施单元测试?我想检测是否有人在Foo不修改比较器的情况下添加了公共属性,因为在这种情况下比较器变得无效。

如果我做类似的事情:

Assert.That(new Foo(42, "answer"), Is.EqualTo(new Foo(42, "answer")));
Run Code Online (Sandbox Code Playgroud)

这无法检测到添加了新属性,并且该属性在两个对象中不同。

有什么办法可以做到这一点吗?

如果可能的话,我们是否可以为某个属性添加一个属性来表示该属性在比较中不相关?

Mar*_*kus 2

您可以使用反射来获取类型的属性,例如:

var knownPropNames = new string[]
{
    "Int", 
    "Str", 
};
var props = typeof(Foo).GetProperties(BindingFlags.Public | BindingFlags.Instance);
var unknownProps = props
                    .Where(x => !knownPropNames.Contains(x.Name))
                    .Select(x => x.Name)
                    .ToArray();
// Use assertion instead of Console.WriteLine
Console.WriteLine("Unknown props: {0}", string.Join("; ", unknownProps));
Run Code Online (Sandbox Code Playgroud)

这样,您可以实现在添加任何属性时失败的测试。当然,您必须在开始时向数组添加新属性。由于从性能角度来看使用反射是一项昂贵的操作,因此如果您需要比较大量对象,我建议在测试中使用它,而不是在比较器本身中使用它。

另请注意参数的使用,BindingFlags以便您可以将属性限制为仅公共属性和实例级别的属性。

此外,您还可以定义一个自定义属性,用于标记不相关的属性。例如:

[AttributeUsage(AttributeTargets.Property)]
public class ComparerIgnoreAttribute : Attribute {}
Run Code Online (Sandbox Code Playgroud)

您可以将其应用于属性:

[ComparerIgnore]
public decimal Dec { get; set; }
Run Code Online (Sandbox Code Playgroud)

此外,您还必须扩展发现未知属性的代码:

var unknownProps = props
                    .Where(x => !knownPropNames.Contains(x.Name) 
                        && !x.GetCustomAttributes(typeof(ComparerIgnoreAttribute)).Any())
                    .Select(x => x.Name)
                    .ToArray();
Run Code Online (Sandbox Code Playgroud)