断言 2 个对象中的所有字段都相同 C#

assert that all fields in 2 objects are the same c#

我在做单元测试,主要是想检查2个对象持有的数据是否相同

Assert.AreEqual(object1, object2);   
Assert.IsTrue(object1.Equals(object2)); //this of course doesn't work

我正在搜索 assertJ

的 C# 等价物
Assert.That(object1).isEqualToComparingFieldByField(object2)

您可以使用记录 (c# 9 +),或者您必须重写 Equals 方法(如果您有权访问并且可以更改您正在使用的对象)。 记录示例:

var point = new Point(3, 4);
var point2 = new Point(3, 4);

var test = point.Equals(point2); //this is true

public record Point(int X, int Y);

与 类:

public class Point
{
    public int X { get; }
    public int Y { get; }


    public override bool Equals(object? obj)
    {
        if (obj == null)
         return false;

        return obj is Point point && (point.X == X && point.Y == Y);
    }

    public override int GetHashCode()
    {
        return HashCode.Combine(X, Y);
    }
}

如果不允许触及实现,则可以使用序列化并比较字符串:

var obj1Str = JsonConvert.SerializeObject(object1);
var obj2Str = JsonConvert.SerializeObject(object2);
Assert.Equal(obj1Str, obj2Str);

使用 Newtonsoft.Json nuget

C# classes 是引用相等性,这意味着使用标准 Equals== 的变量是相同的,如果它们指向同一个对象,您可以覆盖该行为, 但它现在或将来可能会破坏某些东西。

或者,您可以切换到使用默认值相等的构造,structrecord class 都是。如果你不能(或不想)这样做,你可以自己实现一个值等于“helper”的方法。我 建议重写 Equals 方法或 == 运算符,因为这可能(而且很可能会)在将来导致错误,而不是我建议你编写您自己的 ValueEquals 方法或扩展方法,类似于

class Foo
{
    public int Count {get; set;}

    public string Message {get; set;}
}

public static bool ValueEquals(this Foo self, Foo other)
{
    return self.Count == other.Count && self.Message == other.Message;
}

public void MyTest()
{
    // Arrange and Act
    ...

    // Assert
    Assert.IsTrue(myFoo1.ValueEquals(myFoo2));
}

根据您是否可以/想要添加 ValueEquals 到您的 Foo class,您可以决定使用扩展方法还是普通方法。

您还可以实施 IEqualityComparer<T> 之类的

public class FooValueEqualityComparer : IEqualityComparer<Foo>
{
    public bool Equals(Foo foo1, Foo foo2)
    {
        return foo1.Count == foo2.Count &&
               foo1.Message == foo2.Message;
    }

    public int GetHashCode(Foo foo)
    {
        return foo.GetHashCode();
    }
}

// Use it
public void MyTest()
{
    // Arrange and Act
    ...

    // Assert
    Assert.IsTrue(new FooEqualityComparer().Equals(myFoo1, myFoo2));
}


,您可以使用反射编写适用于所有^* class的通用ValueEquals

public static class ValueEqualityComparer
{
    public static bool ValueEquals<T>(this T self, T other) where T : class
    {
        var type = self.GetType();
        if (type == typeof(string))
            return self.Equals(other);

        var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);

        foreach (var property in properties)
        {
            var selfValue = property.GetValue(self);
            var otherValue = property.GetValue(other);

            // String is special, it's not primitive but is value equality like primitives
            if (property.PropertyType.IsPrimitive || property.PropertyType == typeof(string))
            {
                if (!selfValue.Equals(otherValue))
                    return false;
            }
            // If the property is a List value equals each member
            // Maybe find another type that allows indexing and is less restrictive
            else if (typeof(IEnumerable).IsAssignableFrom(property.PropertyType))
            {
                var selfList = ((IEnumerable)property.GetValue(self)).Cast<object>();
                var otherList = ((IEnumerable)property.GetValue(other)).Cast<object>();

                try
                {
                    // Using EquiZip from MoreLinq: https://github.com/morelinq/MoreLINQ/blob/master/MoreLinq/EquiZip.cs
                    foreach (var element in selfList.EquiZip(otherList, (selfItem, otherItem) => new { selfItem, otherItem }))
                    {
                        if (!ValueEquals(element.selfItem, element.otherItem))
                            return false;
                    }
                }
                catch (InvalidOperationException)
                {
                    // MoreLINQ throws a InvalidOperationException if our two enumerables aren't the same length
                    return false;
                }
            }
            else
            {
                if (!ValueEquals(selfValue, otherValue))
                    return false;
            }
        }

        return true;
    }
}

这个实现绝不是完美的,老实说,应该只用于单元测试,也应该彻底测试自己。您可以将我的测试视为 dotnetfiddle here

或者您可以将其“弄脏”并将对象序列化为字符串并比较这些值。