C# 用于检查对象相等性的XUnit断言

C# 用于检查对象相等性的XUnit断言,c#,xunit.net,xunit,C#,Xunit.net,Xunit,我正在使用XUnit框架测试我的C#代码 在这个框架中是否有任何可以进行对象比较的assert方法?我的目的是检查对象的每个公共和私有成员变量是否相等 我尝试过这些替代方案,但很少奏效: 1) bool IsEqual = (Obj1 == Obj2) 2) Assert.Same(Obj1, Obj2) which I couldnt understand what happens internally 在比较对象时,需要有一个自定义比较器来实现这一点,否则将根据它们是否引用内存中的同一对象

我正在使用XUnit框架测试我的C#代码

在这个框架中是否有任何可以进行对象比较的assert方法?我的目的是检查对象的每个公共和私有成员变量是否相等

我尝试过这些替代方案,但很少奏效:

1) bool IsEqual = (Obj1 == Obj2)
2) Assert.Same(Obj1, Obj2) which I couldnt understand what happens internally

在比较对象时,需要有一个自定义比较器来实现这一点,否则将根据它们是否引用内存中的同一对象来检查它们。要覆盖此行为,需要覆盖
Equals
GetHashCode
方法,然后可以执行以下操作:

Assert.True(obj1.Equals(obj2));
以下是MSDN页面abt重载等于方法:


也请注意对这个问题的评论:

我也有类似的问题,但幸运的是,我已经在使用

using Newtonsoft.Json;
所以我只需将其序列化为json对象,然后作为字符串进行比较

var obj1Str = JsonConvert.SerializeObject(obj1);
var obj2Str = JsonConvert.SerializeObject(obj2);
Assert.Equal(obj1Str, obj2Str );

我知道这是一个老问题,但由于我偶然发现了它,我想我应该考虑一个新的可用解决方案(至少在.NETCore2.0解决方案的XUnit2.3.1中)

我不确定它是什么时候引入的,但是现在有一种重载形式的
.Equal
,它接受
IEqualityComparer
的实例作为第三个参数。您可以在单元测试中创建自定义比较器,而不会污染代码

可以这样调用以下代码:
Assert.Equal(expectedParameters,parameters,new CustomComparer())

XUnit似乎会在遇到故障时立即停止处理测试,因此从我们的比较器中抛出一个新的
EqualException
,似乎符合XUnit的开箱操作方式

public class CustomComparer<T> : IEqualityComparer<T>
{
    public bool Equals(T expected, T actual)
    {
        var props = typeof(T).GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Instance);
        foreach (var prop in props)
        {
            var expectedValue = prop.GetValue(expected, null);
            var actualValue = prop.GetValue(actual, null);
            if (!expectedValue.Equals(actualValue))
            {
                throw new EqualException($"A value of \"{expectedValue}\" for property \"{prop.Name}\"",
                    $"A value of \"{actualValue}\" for property \"{prop.Name}\"");
            }
        }

        return true;
    }

    public int GetHashCode(T parameterValue)
    {
        return Tuple.Create(parameterValue).GetHashCode();
    }
}
公共类CustomComparer:IEqualityComparer
{
公共布尔等于(T预期值,T实际值)
{
var props=typeof(T).GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Instance);
foreach(道具中的var道具)
{
var expectedValue=prop.GetValue(预期,空);
var actualValue=prop.GetValue(实际值,null);
如果(!expectedValue.Equals(actualValue))
{
为属性\“{prop.Name}\”抛出新的EqualException($”值\“{expectedValue}\”,
$“属性\“{prop.Name}\”的\“{actualValue}\”值);
}
}
返回true;
}
public int GetHashCode(T参数值)
{
返回Tuple.Create(parameterValue).GetHashCode();
}
}

编辑:我发现将实际值和预期值与
进行比较=
对某些类型无效(我确信有更好的解释涉及引用类型和值类型之间的差异,但这不是今天的解释)。我更新了代码,使用
.Equals
方法来比较这两个值,这似乎效果更好。

有NuGet软件包可以为您这样做。 以下是我个人使用的两个例子

  • :

  • :

  • 库中有一些非常强大的比较逻辑

    myObject.ShouldBeEquivalentTo(new { SomeProperty = "abc", SomeOtherProperty = 23 });
    
    您甚至可以使用它对“myObject”的一部分进行断言。
    但是,它可能无法帮助您处理私有字段。

    xUnit中有“深度比较”。您必须为对象实现IEquatable,然后Assert.Equals将起作用。
    Assert.Same()
    通过引用进行比较;它断言
    Obj1
    Obj2
    是同一个对象,而不仅仅是外观相同。我知道通过实现自定义的“Equals”方法,可以执行此检查。但是有没有什么方法可以进行盲字节比较,从而使检查更容易?这是因为为了单元测试,我将在“正在测试的软件”中使用“Equals”实现。但它附带了一个约束,即向我的类添加[serializable]属性,该类具有私有成员变量。我想从设计角度来看,这是不好的。我反对仅为单元测试重写这两种方法。当业务规则相等的逻辑与测试相等的逻辑不同时,会发生什么情况?您只需要将IEqualityComparer作为第三个参数“Assert.Equal”(expectedCar、actualCar、CarComparer)`我认为这比执行平等方法更有用,因为我希望我的断言失败告诉我什么是错误的。理想情况下,我想要的是能够遍历对象树并积累关于哪些属性/子树不相等的信息,并在这些信息中失败。@Rikkibibson实现Equals不是正确的方法吗?对于每个不同的属性,只需在equals中的列表中添加异常,然后在最后返回true或抛出所有异常是的,但逐案处理可能会非常困难,这就是为什么我过去在比较普通旧对象的树时倾向于寻找基于反射的解决方案,原语和集合。@Rikkibibson有一些NuGet包可以满足您的需要。看看我的答案。这是可行的,但我宁愿使用一个NuGet包来做类似的事情。我需要担心的代码更少您不应该在
    Equals上抛出
    。它可能会搞砸Assert.DoesNotContain
    ,因为它希望在每个条目上都得到false。您最好返回
    false
    。@maracuja确实没有多少代码,但依赖于外部软件包,以及随之而来的所有网络风险……对这些库的利弊有什么看法?@WillP。DeepEqual还没有官方的.NET标准/核心支持。这是可行的,但可能会引起问题。否则,它们几乎是一样的。ExpectedObject还有一些特性,比如部分比较或自定义比较。这就是为什么我现在在几乎所有的项目中都使用ExpectedObject,但是
    [Fact]
    public void RetrievingACustomer_ShouldReturnTheExpectedCustomer()
    {
      // Arrange
      var expectedCustomer = new Customer
      {
        FirstName = "Silence",
        LastName = "Dogood",
        Address = new Address
        {
          AddressLineOne = "The New-England Courant",
          AddressLineTwo = "3 Queen Street",
          City = "Boston",
          State = "MA",
          PostalCode = "02114"
        }                                            
      }.ToExpectedObject();
    
    
      // Act
      var actualCustomer = new CustomerService().GetCustomerByName("Silence", "Dogood");
    
      // Assert
      expectedCustomer.ShouldEqual(actualCustomer);
    }
    
    myObject.ShouldBeEquivalentTo(new { SomeProperty = "abc", SomeOtherProperty = 23 });