【问题标题】:XUnit Assertion for checking equality of objectsXUnit Assertion 用于检查对象的相等性
【发布时间】:2012-06-23 12:30:27
【问题描述】:

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

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

我尝试了这些替代方法,但很少奏效:

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

【问题讨论】:

  • xUnit 中有“深度比较”。您必须为您的对象实现 IEquatable,然后 Assert.Equals 将起作用。
  • Assert.Same() 按引用比较;它断言Obj1Obj2相同 对象,而不仅仅是看起来 相同。

标签: c# xunit.net xunit


【解决方案1】:

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

using Newtonsoft.Json;

所以我只需要将它序列化为 json 对象,然后作为字符串进行比较。

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

【讨论】:

  • 我认为这比实现 Equals 方法更有用,因为我希望我的断言失败告诉我什么是错误的。理想情况下,我希望有一些东西可以遍历对象树并累积有关哪些属性/子树不相等的信息,并因该信息而失败。
  • @RikkiGibson 实施 Equals 的方式不正确吗?只需为每个不同的属性在等于列表中添加异常,然后在最后返回 true 或抛出所有异常
  • 是的,但逐案处理可能会很辛苦,这就是为什么我过去在比较普通旧对象、基元和集合的树时倾向于寻找基于反射的解决方案。
  • @RikkiGibson 有一些 NuGet 包可以满足您的需求。看我的回答。
【解决方案2】:

FluentAssertions 库内部有一些非常强大的比较逻辑。

myObject.ShouldBeEquivalentTo(new { SomeProperty = "abc", SomeOtherProperty = 23 });

您甚至可以使用它来断言“myObject”的一部分。 但是,它可能无法帮助您处理私有字段。

【讨论】:

  • 当前版本的库似乎使用以下 API:actual.Should().BeEquivalentTo(expected);
  • 我从我的岩石下爬出来,面对我必须面对的 xUnit 测试怪物......这太没有意义了,我说。我的内心编码孩子总是必须被提醒单元测试发脾气是如此完全不适合年龄。嗯,那是在我发现 FluentAssertions 之前。那一天就是今天。而你,@AdrianPetrescu 衷心感谢这位睡眼惺忪的开发者。
【解决方案3】:

你需要有一个自定义的比较器来实现这一点,当你比较对象时,它们会根据它们是否引用内存中的同一个对象来检查。要覆盖此行为,您需要覆盖 EqualsGetHashCode 方法,然后您可以这样做:

Assert.True(obj1.Equals(obj2));

这是一个 MSDN 页面 abt 重载 Equals 方法:http://msdn.microsoft.com/en-us/library/ms173147(v=vs.80).aspx

还可以对问题发表评论:What's the difference between IEquatable and just overriding Object.Equals()?

【讨论】:

  • 我了解通过实现自定义“Equals”方法,可以执行此检查。但是有没有什么方法可以进行盲字节比较,让检查更容易呢?这是因为我最终会在“被测软件”中实现“Equals”,只是为了进行单元测试。
  • 我尝试使用序列化方法将两个对象都转换为字节数组,并且成功了。但它带有一个限制,即向我的具有私有成员变量的类添加 [serializable] 属性。我想这从设计角度来看并不好
  • 我反对仅仅为了单元测试而重写这两种方法。
  • 当您的业务规则相等逻辑与测试相等逻辑不同时会发生什么?
  • 你只需要传递一个 IEqualityComparer 作为第三个参数 `Assert.Equal(expectedCar, actualCar, CarComparer); `
【解决方案4】:

有 NuGet 包可以为您执行此操作。 这是我个人使用的两个示例。

  1. DeepEqual:

    object1.ShouldDeepEqual(object2);
    
  2. ExpectedObjects:

    [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);
    }
    

【讨论】:

  • 对这些库的优缺点有何看法?
  • @WillP。 DeepEqual 还没有官方的 .NET Standard/Core 支持。它有效,但可能会导致问题。否则它们几乎相同。 ExpectedObjects 具有更多功能,例如部分或自定义比较。这就是我现在在几乎所有项目中都使用 ExpectedObjects 的原因,但这更多的是个人喜好。
【解决方案5】:

我知道这是一个老问题,但由于我偶然发现它,我想我会考虑一个可用的新解决方案(至少在 xunit 2.3.1 中的 .net Core 2.0 解决方案中)。

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

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

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();
    }
}

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

【讨论】:

  • 这可行,但我宁愿只使用执行类似操作的 NuGet 包。更少我需要担心的代码
  • 您不应该在Equals 上使用throw。它可能会搞砸Assert.DoesNotContain,因为它预计每个条目都会出错。你最好返回false
  • @maracuja-juice 确实少了代码,但依赖于外部包,以及随之而来的所有网络风险......
  • 由于这个答案在 3 年多后继续获得支持,我将继续指出我创建了一个开源 NuGet 包来执行此操作,它比我的更强大已经包括在这里。来源:github.com/engineer-andrew/ea-xunit-helpers 封装:nuget.org/packages/EAXUnitHelpers.Comparison
  • @Necriis 或者,如果您不想要外部包:创建自己的函数,以便封装代码并且更易于使用,而不必总是使用 CustomComparer。
【解决方案6】:

这里是 IEqualityComparer 接口的另一个实现,真正的 GenericComparer

为了断言 2 个相同类型的列表是相等的,无论列表容器如何,您都可以添加此扩展:

public static bool IsEqualTo<T>(this IEnumerable<T> list, IEnumerable<T> expected) where T : class
{
    if (!list.HasSameLengthThan<T>(expected))
        return false;

    Assert.Equal(expected, list, new GenericComparer<T>());
    return true;
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-07-19
    • 1970-01-01
    • 2016-04-18
    • 2019-03-17
    • 1970-01-01
    • 2019-06-26
    相关资源
    最近更新 更多