【问题标题】:Compare equality between two objects in NUnit比较 NUnit 中两个对象之间的相等性
【发布时间】:2010-09-24 00:14:39
【问题描述】:

我试图断言一个对象与另一个对象“相等”。

对象只是具有一堆公共属性的类的实例。有没有一种简单的方法可以让 NUnit 根据属性断言相等?

这是我目前的解决方案,但我认为可能会有更好的解决方案:

Assert.AreEqual(LeftObject.Property1, RightObject.Property1)
Assert.AreEqual(LeftObject.Property2, RightObject.Property2)
Assert.AreEqual(LeftObject.Property3, RightObject.Property3)
...
Assert.AreEqual(LeftObject.PropertyN, RightObject.PropertyN)

我所追求的将与 CollectionEquivalentConstraint 具有相同的精神,其中 NUnit 验证两个集合的内容是否相同。

【问题讨论】:

    标签: c# unit-testing automated-tests nunit


    【解决方案1】:

    为您的对象覆盖 .Equals,然后在单元测试中您可以简单地执行以下操作:

    Assert.AreEqual(LeftObject, RightObject);
    

    当然,这可能意味着您只需将所有单独的比较移至 .Equals 方法,但它允许您在多个测试中重用该实现,并且如果对象应该能够与还是兄弟姐妹。

    【讨论】:

    • 谢谢,lassevk。这对我有用!我根据此处的指南实施了 .Equals:msdn.microsoft.com/en-us/library/336aedhh(VS.80).aspx
    • 还有 GetHashCode(),很明显 ;-p
    • 一个重要的警告:如果你的对象也实现了IEnumerable,那么无论Equals的覆盖实现如何,它都会被作为一个集合进行比较,因为NUnit给IEnumerable更高的优先级。有关详细信息,请参阅NUnitEqualityComparer.AreEqual 方法。您可以使用等式约束的Using() 方法之一覆盖比较器。即使这样,由于 NUnit 使用了适配器,实现非泛型 IEqualityComparer 还不够。
    • 更多警告:在可变类型上实现GetHashCode() 如果您曾经将该对象用作键,则会出现异常行为。恕我直言,覆盖 Equals()GetHashCode() 并使对象不可变只是为了测试是没有意义的。
    【解决方案2】:

    如果由于任何原因无法覆盖 Equals,则可以构建一个辅助方法,通过反射遍历公共属性并断言每个属性。像这样的:

    public static class AssertEx
    {
        public static void PropertyValuesAreEquals(object actual, object expected)
        {
            PropertyInfo[] properties = expected.GetType().GetProperties();
            foreach (PropertyInfo property in properties)
            {
                object expectedValue = property.GetValue(expected, null);
                object actualValue = property.GetValue(actual, null);
    
                if (actualValue is IList)
                    AssertListsAreEquals(property, (IList)actualValue, (IList)expectedValue);
                else if (!Equals(expectedValue, actualValue))
                    Assert.Fail("Property {0}.{1} does not match. Expected: {2} but was: {3}", property.DeclaringType.Name, property.Name, expectedValue, actualValue);
            }
        }
    
        private static void AssertListsAreEquals(PropertyInfo property, IList actualList, IList expectedList)
        {
            if (actualList.Count != expectedList.Count)
                Assert.Fail("Property {0}.{1} does not match. Expected IList containing {2} elements but was IList containing {3} elements", property.PropertyType.Name, property.Name, expectedList.Count, actualList.Count);
    
            for (int i = 0; i < actualList.Count; i++)
                if (!Equals(actualList[i], expectedList[i]))
                    Assert.Fail("Property {0}.{1} does not match. Expected IList with element {1} equals to {2} but was IList with element {1} equals to {3}", property.PropertyType.Name, property.Name, expectedList[i], actualList[i]);
        }
    }
    

    【讨论】:

    • @wesley:这不是真的。 Type.GetProperties 方法:返回当前 Type 的所有公共属性。见msdn.microsoft.com/en-us/library/aky14axb.aspx
    • 谢谢。但是,我不得不切换实际参数和预期参数的顺序,因为转换是预期是实际参数之前的参数。
    • 这是一个更好的方法恕我直言,Equal & HashCode 覆盖不应该基于比较每个字段,而且在每个对象上都非常乏味。干得好!
    • 如果您的类型只有基本类型作为属性,这非常有用。但是,如果您的类型具有自定义类型的属性(不实现 Equals),它将失败。
    • 为对象属性添加了一些递归,但我不得不跳过索引属性:
    【解决方案3】:

    我不希望仅仅为了启用测试而覆盖 Equals。不要忘记,如果你确实覆盖了 Equals,你真的应该也覆盖 GetHashCode,否则如果你在字典中使用你的对象,你可能会得到意想不到的结果。

    我确实喜欢上面的反射方法,因为它可以满足将来添加的属性。

    但是,对于一个快速简单的解决方案,通常最简单的方法是创建一个帮助方法来测试对象是否相等,或者在您对测试保持私有的类上实现 IEqualityComparer。使用 IEqualityComparer 解决方案时,您无需费心执行 GetHashCode。例如:

    // Sample class.  This would be in your main assembly.
    class Person
    {
        public string Name { get; set; }
        public int Age { get; set; }
    }
    
    // Unit tests
    [TestFixture]
    public class PersonTests
    {
        private class PersonComparer : IEqualityComparer<Person>
        {
            public bool Equals(Person x, Person y)
            {
                if (x == null && y == null)
                {
                    return true;
                }
    
                if (x == null || y == null)
                {
                    return false;
                }
    
                return (x.Name == y.Name) && (x.Age == y.Age);
            }
    
            public int GetHashCode(Person obj)
            {
                throw new NotImplementedException();
            }
        }
    
        [Test]
        public void Test_PersonComparer()
        {
            Person p1 = new Person { Name = "Tom", Age = 20 }; // Control data
    
            Person p2 = new Person { Name = "Tom", Age = 20 }; // Same as control
            Person p3 = new Person { Name = "Tom", Age = 30 }; // Different age
            Person p4 = new Person { Name = "Bob", Age = 20 }; // Different name.
    
            Assert.IsTrue(new PersonComparer().Equals(p1, p2), "People have same values");
            Assert.IsFalse(new PersonComparer().Equals(p1, p3), "People have different ages.");
            Assert.IsFalse(new PersonComparer().Equals(p1, p4), "People have different names.");
        }
    }
    

    【讨论】:

    • equals 不处理空值。我会在 equals 方法中的 return 语句之前添加以下内容。 if (x == null && y == null) { return true; } if (x == null || y == null) { return false; } 我编辑了问题以添加空支持。
    • 对我不起作用 throw new NotImplementedException();在 GetHashCode 中。为什么我需要在 IEqualityComparer 中使用该功能?
    【解决方案4】:

    我同意 ChrisYoxall 的观点——纯粹出于测试目的在您的主代码中实现 Equals 并不好。

    如果您因为某些应用程序逻辑需要而实现 Equals,那很好,但请避免纯测试代码造成混乱(同样检查相同以进行测试的语义可能与您的应用程序所需的不同) .

    简而言之,将仅用于测试的代码排除在课堂之外。

    对于大多数类来说,使用反射对属性进行简单的浅层比较就足够了,尽管如果您的对象具有复杂的属性,您可能需要递归。如果遵循引用,请注意循环引用或类似内容。

    狡猾

    【讨论】:

    • 很好地抓住了循环引用。如果您已经在比较树中保留了对象字典,则很容易克服。
    【解决方案5】:

    反序列化两个类,并进行字符串比较。

    编辑: 完美运行,这是我从 NUnit 得到的输出;

    Test 'Telecom.SDP.SBO.App.Customer.Translator.UnitTests.TranslateEaiCustomerToDomain_Tests.TranslateNew_GivenEaiCustomer_ShouldTranslateToDomainCustomer_Test("ApprovedRatingInDb")' failed:
      Expected string length 2841 but was 5034. Strings differ at index 443.
      Expected: "...taClasses" />\r\n  <ContactMedia />\r\n  <Party i:nil="true" /..."
      But was:  "...taClasses" />\r\n  <ContactMedia>\r\n    <ContactMedium z:Id="..."
      ----------------------------------------------^
     TranslateEaiCustomerToDomain_Tests.cs(201,0): at Telecom.SDP.SBO.App.Customer.Translator.UnitTests.TranslateEaiCustomerToDomain_Tests.Assert_CustomersAreEqual(Customer expectedCustomer, Customer actualCustomer)
     TranslateEaiCustomerToDomain_Tests.cs(114,0): at Telecom.SDP.SBO.App.Customer.Translator.UnitTests.TranslateEaiCustomerToDomain_Tests.TranslateNew_GivenEaiCustomer_ShouldTranslateToDomainCustomer_Test(String custRatingScenario)
    

    编辑二: 这两个对象可以相同,但属性序列化的顺序不同。因此 XML 是不同的。哦!

    编辑三: 这确实有效。我在我的测试中使用它。但是您必须按照被测代码添加项目的顺序将项目添加到集合属性中。

    【讨论】:

    • 序列化?有趣的想法。不过,我不确定它在性能方面的表现如何
    • 不允许您比较具有给定精度的双精度数或小数。
    【解决方案6】:

    试试FluentAssertions库:

    dto.Should().BeEquivalentTo(customer) 
    

    也可以使用 NuGet 安装。

    【讨论】:

    • ShouldHave 已被弃用,因此应该是 dto.ShouldBeEquivalentTo(customer);而是
    • 这是this reason的最佳答案。
    • 刚刚遇到了同样的问题并使用了以下似乎工作正常:actual.ShouldBeEquivalentTo(expected, x =&gt; x.ExcludingMissingMembers())
    • 这是一个很棒的库!不需要覆盖 Equals 并且(如果无论如何都覆盖了 equals,例如对于值对象)不依赖于正确的实现。也可以很好地打印差异,就像 Hamcrest 为 Java 所做的那样。
    • 在 5.10.3 版本中的语法是 dto.Should().BeEquivalentTo(customer)
    【解决方案7】:

    不要仅仅为了测试目的而覆盖 Equals。这很乏味并且影响域逻辑。 相反,

    使用 JSON 比较对象的数据

    您的对象没有额外的逻辑。没有额外的测试任务。

    只要用这个简单的方法:

    public static void AreEqualByJson(object expected, object actual)
    {
        var serializer = new System.Web.Script.Serialization.JavaScriptSerializer();
        var expectedJson = serializer.Serialize(expected);
        var actualJson = serializer.Serialize(actual);
        Assert.AreEqual(expectedJson, actualJson);
    }
    

    看起来效果很好。测试运行器结果信息将显示包含的 JSON 字符串比较(对象图),以便您直接看到问题所在。

    另请注意!如果您有更大的复杂对象并且只想比较它们的一部分,您可以(使用 LINQ 处理序列数据)创建匿名对象以与上面一起使用方法。

    public void SomeTest()
    {
        var expect = new { PropA = 12, PropB = 14 };
        var sut = loc.Resolve<SomeSvc>();
        var bigObjectResult = sut.Execute(); // This will return a big object with loads of properties 
        AssExt.AreEqualByJson(expect, new { bigObjectResult.PropA, bigObjectResult.PropB });
    }
    

    【讨论】:

    • 这是一种很好的测试方式,尤其是当您无论如何都在处理 JSON 时(例如,使用类型化客户端访问 Web 服务)。这个答案应该更高。
    • 使用 Linq! @DmitryBLR(见答案的最后一段):)
    • 这是个好主意。我会使用较新的 Json.NET: var expectedJson = Newtonsoft.Json.JsonConvert.SerializeObject(expected);
    • 这不适用于循环引用。改用 github.com/kbilsted/StatePrinter 以获得优于 JSON 方法的体验
    • 这是真的@KokaChernov,有时如果排序不同,您希望测试失败,但如果排序不同,您不想失败,您可以在在将它们传递给 AreEqualByJson 方法之前列出它们。在测试之前“重新排列”对象的简单变体在答案的最后一个代码示例中。所以我认为这是非常“普遍”的! :)
    【解决方案8】:

    另一种选择是通过实现 NUnit 抽象 Constraint 类来编写自定义约束。使用帮助类提供一点语法糖,生成的测试代码非常简洁易读,例如

    Assert.That( LeftObject, PortfolioState.Matches( RightObject ) ); 
    

    举个极端的例子,考虑具有“只读”成员的类不是IEquatable,即使您想更改测试中的类也无法更改:

    public class Portfolio // Somewhat daft class for pedagogic purposes...
    {
        // Cannot be instanitated externally, instead has two 'factory' methods
        private Portfolio(){ }
    
        // Immutable properties
        public string Property1 { get; private set; }
        public string Property2 { get; private set; }  // Cannot be accessed externally
        public string Property3 { get; private set; }  // Cannot be accessed externally
    
        // 'Factory' method 1
        public static Portfolio GetPortfolio(string p1, string p2, string p3)
        {
            return new Portfolio() 
            { 
                Property1 = p1, 
                Property2 = p2, 
                Property3 = p3 
            };
        }
    
        // 'Factory' method 2
        public static Portfolio GetDefault()
        {
            return new Portfolio() 
            { 
                Property1 = "{{NONE}}", 
                Property2 = "{{NONE}}", 
                Property3 = "{{NONE}}" 
            };
        }
    }
    

    Constraint 类的合同要求一个覆盖 MatchesWriteDescriptionTo(在不匹配的情况下,对预期值的叙述),但也覆盖 WriteActualValueTo(对实际值的叙述)使得感觉:

    public class PortfolioEqualityConstraint : Constraint
    {
        Portfolio expected;
        string expectedMessage = "";
        string actualMessage = "";
    
        public PortfolioEqualityConstraint(Portfolio expected)
        {
            this.expected = expected;
        }
    
        public override bool Matches(object actual)
        {
            if ( actual == null && expected == null ) return true;
            if ( !(actual is Portfolio) )
            { 
                expectedMessage = "<Portfolio>";
                actualMessage = "null";
                return false;
            }
            return Matches((Portfolio)actual);
        }
    
        private bool Matches(Portfolio actual)
        {
            if ( expected == null && actual != null )
            {
                expectedMessage = "null";
                expectedMessage = "non-null";
                return false;
            }
            if ( ReferenceEquals(expected, actual) ) return true;
    
            if ( !( expected.Property1.Equals(actual.Property1)
                     && expected.Property2.Equals(actual.Property2) 
                     && expected.Property3.Equals(actual.Property3) ) )
            {
                expectedMessage = expected.ToStringForTest();
                actualMessage = actual.ToStringForTest();
                return false;
            }
            return true;
        }
    
        public override void WriteDescriptionTo(MessageWriter writer)
        {
            writer.WriteExpectedValue(expectedMessage);
        }
        public override void WriteActualValueTo(MessageWriter writer)
        {
            writer.WriteExpectedValue(actualMessage);
        }
    }
    

    加上辅助类:

    public static class PortfolioState
    {
        public static PortfolioEqualityConstraint Matches(Portfolio expected)
        {
            return new PortfolioEqualityConstraint(expected);
        }
    
        public static string ToStringForTest(this Portfolio source)
        {
            return String.Format("Property1 = {0}, Property2 = {1}, Property3 = {2}.", 
                source.Property1, source.Property2, source.Property3 );
        }
    }
    

    示例用法:

    [TestFixture]
    class PortfolioTests
    {
        [Test]
        public void TestPortfolioEquality()
        {
            Portfolio LeftObject 
                = Portfolio.GetDefault();
            Portfolio RightObject 
                = Portfolio.GetPortfolio("{{GNOME}}", "{{NONE}}", "{{NONE}}");
    
            Assert.That( LeftObject, PortfolioState.Matches( RightObject ) );
        }
    }
    

    【讨论】:

      【解决方案9】:

      Max Wikstrom 的 JSON 解决方案(上图)对我来说最有意义,它简短、干净,最重要的是它有效。就个人而言,虽然我更喜欢将 JSON 转换作为一个单独的方法来实现,并将断言放回单元测试中,就像这样......

      帮助方法:

      public string GetObjectAsJson(object obj)
          {
              System.Web.Script.Serialization.JavaScriptSerializer oSerializer = new System.Web.Script.Serialization.JavaScriptSerializer();
              return oSerializer.Serialize(obj);
          }
      

      单元测试:

      public void GetDimensionsFromImageTest()
              {
                  Image Image = new Bitmap(10, 10);
                  ImageHelpers_Accessor.ImageDimensions expected = new ImageHelpers_Accessor.ImageDimensions(10,10);
      
                  ImageHelpers_Accessor.ImageDimensions actual;
                  actual = ImageHelpers_Accessor.GetDimensionsFromImage(Image);
      
                  /*USING IT HERE >>>*/
                  Assert.AreEqual(GetObjectAsJson(expected), GetObjectAsJson(actual));
              }
      

      仅供参考 - 您可能需要在解决方案中添加对 System.Web.Extensions 的引用。

      【讨论】:

        【解决方案10】:

        我会以@Juanma 的回答为基础。但是,我认为这不应该通过单元测试断言来实现。这是一个在某些情况下可以由非测试代码很好地使用的实用程序。

        我写了一篇关于此事的文章http://timoch.com/blog/2013/06/unit-test-equality-is-not-domain-equality/

        我的建议如下:

        /// <summary>
        /// Returns the names of the properties that are not equal on a and b.
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns>An array of names of properties with distinct 
        ///          values or null if a and b are null or not of the same type
        /// </returns>
        public static string[] GetDistinctProperties(object a, object b) {
            if (object.ReferenceEquals(a, b))
                return null;
            if (a == null)
                return null;
            if (b == null)
                return null;
        
            var aType = a.GetType();
            var bType = b.GetType();
        
            if (aType != bType)
                return null;
        
            var props = aType.GetProperties();
        
            if (props.Any(prop => prop.GetIndexParameters().Length != 0))
                throw new ArgumentException("Types with index properties not supported");
        
            return props
                .Where(prop => !Equals(prop.GetValue(a, null), prop.GetValue(b, null)))
                .Select(prop => prop.Name).ToArray();
        } 
        

        在 NUnit 中使用它

        Expect(ReflectionUtils.GetDistinctProperties(tile, got), Empty);
        

        在不匹配时产生以下消息。

        Expected: <empty>
        But was:  < "MagmaLevel" >
        at NUnit.Framework.Assert.That(Object actual, IResolveConstraint expression, String message, Object[] args)
        at Undermine.Engine.Tests.TileMaps.BasicTileMapTests.BasicOperations() in BasicTileMapTests.cs: line 29
        

        【讨论】:

          【解决方案11】:

          https://github.com/kbilsted/StatePrinter 专门用于将对象图转储为字符串表示,目的是编写简单的单元测试。

          • 它带有 witg Assert 方法,可以输出正确转义的字符串,轻松复制粘贴到测试中以更正它。
          • 它允许自动重写单元测试
          • 它与所有单元测试框架集成
          • 与 JSON 序列化不同,支持循环引用
          • 您可以轻松过滤,因此只转储部分类型

          给定

          class A
          {
            public DateTime X;
            public DateTime Y { get; set; }
            public string Name;
          }
          

          您可以以类型安全的方式,并使用 Visual Studio 的自动完成包含或排除字段。

            var printer = new Stateprinter();
            printer.Configuration.Projectionharvester().Exclude<A>(x => x.X, x => x.Y);
          
            var sut = new A { X = DateTime.Now, Name = "Charly" };
          
            var expected = @"new A(){ Name = ""Charly""}";
            printer.Assert.PrintIsSame(expected, sut);
          

          【讨论】:

            【解决方案12】:

            我尝试了这里提到的几种方法。大多数涉及序列化您的对象并进行字符串比较。虽然超级简单且通常非常有效,但我发现当您遇到故障并报告类似这样的事情时,它会有点短:

            Expected string length 2326 but was 2342. Strings differ at index 1729.
            

            至少可以说,弄清楚差异在哪里是一件痛苦的事。

            使用 FluentAssertions 的object graph comparisons(即a.ShouldBeEquivalentTo(b)),您可以得到这个:

            Expected property Name to be "Foo" but found "Bar"
            

            这样好多了。 Get FluentAssertions 现在,您稍后会很高兴(如果您对此表示赞同,也请在第一次建议 FluentAssertions 时为 dkl's answer 投票)。

            【讨论】:

              【解决方案13】:

              只需从 Nuget 安装 ExpectedObjects,您就可以轻松比较两个对象的属性值、集合的每个对象值、两个组合对象的值以及匿名类型的部分比较属性值。

              我在 github 上有一些例子:https://github.com/hatelove/CompareObjectEquals

              以下是一些包含比较对象场景的示例:

                  [TestMethod]
                  public void Test_Person_Equals_with_ExpectedObjects()
                  {
                      //use extension method ToExpectedObject() from using ExpectedObjects namespace to project Person to ExpectedObject
                      var expected = new Person
                      {
                          Id = 1,
                          Name = "A",
                          Age = 10,
                      }.ToExpectedObject();
              
                      var actual = new Person
                      {
                          Id = 1,
                          Name = "A",
                          Age = 10,
                      };
              
                      //use ShouldEqual to compare expected and actual instance, if they are not equal, it will throw a System.Exception and its message includes what properties were not match our expectation.
                      expected.ShouldEqual(actual);
                  }
              
                  [TestMethod]
                  public void Test_PersonCollection_Equals_with_ExpectedObjects()
                  {
                      //collection just invoke extension method: ToExpectedObject() to project Collection<Person> to ExpectedObject too
                      var expected = new List<Person>
                      {
                          new Person { Id=1, Name="A",Age=10},
                          new Person { Id=2, Name="B",Age=20},
                          new Person { Id=3, Name="C",Age=30},
                      }.ToExpectedObject();
              
                      var actual = new List<Person>
                      {
                          new Person { Id=1, Name="A",Age=10},
                          new Person { Id=2, Name="B",Age=20},
                          new Person { Id=3, Name="C",Age=30},
                      };
              
                      expected.ShouldEqual(actual);
                  }
              
                  [TestMethod]
                  public void Test_ComposedPerson_Equals_with_ExpectedObjects()
                  {
                      //ExpectedObject will compare each value of property recursively, so composed type also simply compare equals.
                      var expected = new Person
                      {
                          Id = 1,
                          Name = "A",
                          Age = 10,
                          Order = new Order { Id = 91, Price = 910 },
                      }.ToExpectedObject();
              
                      var actual = new Person
                      {
                          Id = 1,
                          Name = "A",
                          Age = 10,
                          Order = new Order { Id = 91, Price = 910 },
                      };
              
                      expected.ShouldEqual(actual);
                  }
              
                  [TestMethod]
                  public void Test_PartialCompare_Person_Equals_with_ExpectedObjects()
                  {
                      //when partial comparing, you need to use anonymous type too. Because only anonymous type can dynamic define only a few properties should be assign.
                      var expected = new
                      {
                          Id = 1,
                          Age = 10,
                          Order = new { Id = 91 }, // composed type should be used anonymous type too, only compare properties. If you trace ExpectedObjects's source code, you will find it invoke config.IgnoreType() first.
                      }.ToExpectedObject();
              
                      var actual = new Person
                      {
                          Id = 1,
                          Name = "B",
                          Age = 10,
                          Order = new Order { Id = 91, Price = 910 },
                      };
              
                      // partial comparing use ShouldMatch(), rather than ShouldEqual()
                      expected.ShouldMatch(actual);
                  }
              

              参考:

              1. ExpectedObjects github
              2. Introduction of ExpectedObjects

              【讨论】:

                【解决方案14】:

                Property constraints,在 NUnit 2.4.2 中添加,允许比 OP 的原始解决方案更具可读性,并产生更好的失败消息。它绝不是通用的,但如果你不需要为太多的类做它,它是一个非常合适的解决方案。

                Assert.That(ActualObject, Has.Property("Prop1").EqualTo(ExpectedObject.Prop1)
                                          & Has.Property("Prop2").EqualTo(ExpectedObject.Prop2)
                                          & Has.Property("Prop3").EqualTo(ExpectedObject.Prop3)
                                          // ...
                

                不像实现 Equals 那样通用,但它确实提供了比实现更好的失败消息

                Assert.AreEqual(ExpectedObject, ActualObject);
                

                【讨论】:

                  【解决方案15】:

                  这是一个相当古老的线程,但我想知道为什么没有建议 NUnit.Framework.Is.EqualToNUnit.Framework.Is.NotEqualTo 的答案是否有原因?

                  如:

                  Assert.That(LeftObject, Is.EqualTo(RightObject)); 
                  

                  Assert.That(LeftObject, Is.Not.EqualTo(RightObject)); 
                  

                  【讨论】:

                  • 因为它没有打印出细节有什么不同
                  【解决方案16】:

                  字符串化和比较两个字符串

                  Assert.AreEqual(JSON.stringify(LeftObject), JSON.stringify(RightObject))

                  【讨论】:

                  • 我猜这是因为使用 JavaScript 方法而被否决了?
                  【解决方案17】:

                  我已经写了一个简单的表达式工厂:

                  public static class AllFieldsEqualityComprision<T>
                  {
                      public static Comparison<T> Instance { get; } = GetInstance();
                  
                      private static Comparison<T> GetInstance()
                      {
                          var type = typeof(T);
                          ParameterExpression[] parameters =
                          {
                              Expression.Parameter(type, "x"),
                              Expression.Parameter(type, "y")
                          };
                          var result = type.GetProperties().Aggregate<PropertyInfo, Expression>(
                              Expression.Constant(true),
                              (acc, prop) =>
                                  Expression.And(acc,
                                      Expression.Equal(
                                          Expression.Property(parameters[0], prop.Name),
                                          Expression.Property(parameters[1], prop.Name))));
                          var areEqualExpression = Expression.Condition(result, Expression.Constant(0), Expression.Constant(1));
                          return Expression.Lambda<Comparison<T>>(areEqualExpression, parameters).Compile();
                      }
                  }
                  

                  然后使用它:

                  Assert.That(
                      expectedCollection, 
                      Is.EqualTo(actualCollection)
                        .Using(AllFieldsEqualityComprision<BusinessCategoryResponse>.Instance));
                  

                  这非常有用,因为我必须比较这些对象的集合。你可以在其他地方使用这个比较:)

                  这里是示例的要点:https://gist.github.com/Pzixel/b63fea074864892f9aba8ffde312094f

                  【讨论】:

                    【解决方案18】:

                    我知道这是一个非常老的问题,但 NUnit 仍然没有对此的原生支持。但是,如果您喜欢 BDD 风格的测试(ala Jasmine),您会对 NExpect(https://github.com/fluffynuts/NExpect,从 NuGet 获得)感到惊喜,它内置了深度相等测试。

                    (免责声明:我是 NExpect 的作者)

                    【讨论】:

                      猜你喜欢
                      • 1970-01-01
                      • 1970-01-01
                      • 2016-09-07
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      相关资源
                      最近更新 更多