简体   繁体   English

C# - 在单元测试中断言两个对象相等

[英]C# - Asserting two objects are equal in unit tests

Either using Nunit or Microsoft.VisualStudio.TestTools.UnitTesting.使用 Nunit 或 Microsoft.VisualStudio.TestTools.UnitTesting。 Right now my assertion fails.现在我的断言失败了。

    [TestMethod]
    public void GivenEmptyBoardExpectEmptyBoard()
    {
        var test = new Board();

        var input = new Board()
            {
                Rows = new List<Row>()
                    {
                        new Row(){Cells = new List<int>(){0,0,0,0}},
                        new Row(){Cells = new List<int>(){0,0,0,0}},
                        new Row(){Cells = new List<int>(){0,0,0,0}},
                        new Row(){Cells = new List<int>(){0,0,0,0}},
                    }
            };

        var expected = new Board()
        {
            Rows = new List<Row>()
                    {
                        new Row(){Cells = new List<int>(){0,0,0,0}},
                        new Row(){Cells = new List<int>(){0,0,0,0}},
                        new Row(){Cells = new List<int>(){0,0,0,0}},
                        new Row(){Cells = new List<int>(){0,0,0,0}},
                    }
        };

        var lifeOrchestration = new LifeOrchestration();

        var actual = lifeOrchestration.Evolve(input);

        Assert.AreEqual(expected, actual);
    }

You've got two different Board instances, so your call to Assert.AreEqual will fail.您有两个不同的Board实例,因此您对Assert.AreEqual的调用将失败。 Even if their entire contents appear to be the same, you're comparing references, not the underlying values.即使它们的全部内容看起来相同,您也是在比较参考,而不是基础值。

You have to specify what makes two Board instances equal.您必须指定使两个Board实例相等的原因。

You can do it in your test:你可以在你的测试中做到这一点:

Assert.AreEqual(expected.Rows.Count, actual.Rows.Count);
Assert.AreEqual(expected.Rows[0].Cells[0], actual.Rows[0].Cells[0]);

// Lots more tests of equality...

Or you can do it in your classes: (note I wrote this on-the-fly - you'll want to adjust this)或者你可以在你的课堂上做:(注意我是即时写的——你需要调整它)

public class Board
{
    public List<Row> Rows = new List<Row>();

    public override bool Equals(object obj)
    {
        var board = obj as Board;

        if (board == null)
            return false;

        if (board.Rows.Count != Rows.Count)
            return false;

        return !board.Rows.Where((t, i) => !t.Equals(Rows[i])).Any();
    }

    public override int GetHashCode()
    {
        // determine what's appropriate to return here - a unique board id may be appropriate if available
    }
}

public class Row
{
    public List<int> Cells = new List<int>(); 

    public override bool Equals(object obj)
    {
        var row = obj as Row;

        if (row == null)
            return false;

        if (row.Cells.Count != Cells.Count)
            return false;

        if (row.Cells.Except(Cells).Any())
            return false;

        return true;
    }

    public override int GetHashCode()
    {
        // determine what's appropriate to return here - a unique row id may be appropriate if available
    }
}

I used to override getHasCode and equals, but I never liked it since I don't want to change my production code for the sake of unit testing.我曾经重写 getHasCode 和 equals,但我从不喜欢它,因为我不想为了单元测试而更改我的生产代码。 Also it's kind of pain.也是一种痛苦。

Then I turned too reflection to compare objects which was less invasive...but that's kind of lot of work (lots of corner cases)然后我变得过于反思来比较侵入性较小的对象......但这需要大量工作(很多极端案例)

In the end I use:最后我使用:

http://www.nuget.org/packages/DeepEqual/ Works great. http://www.nuget.org/packages/DeepEqual/效果很好。

Update, 6 years later : 6年后更新

I now use the more general library fluentassertions for .NET it does the same as above but with more features and a nice DSL, the specific replacement would be: https://fluentassertions.com/objectgraphs/我现在为 .NET 使用更通用的库 fluentassertions,它与上面的功能相同,但具有更多功能和一个不错的 DSL,具体的替换是: https ://fluentassertions.com/objectgraphs/

PM> Install-Package FluentAssertions

Also after some years of experience, I still not recommend the override route, I'd even consider it a bad practice.同样经过几年的经验,我仍然不推荐覆盖路线,我什至认为这是一种不好的做法。 If you're not careful you could introduce performance issues when using some Collections like Dictionaries.如果您不小心,可能会在使用某些集合(如字典)时引入性能问题。 Also when the time would come where you will have a real business case to overload these methods you'd be in trouble because you'd have this test code in there already.此外,当你有一个真正的商业案例来重载这些方法时,你会遇到麻烦,因为你已经有这个测试代码了。 Production code and test code should be kept separated, test code should not rely on implementation details or hacks to achieve their goal, this make them hard to maintain and understand.生产代码和测试代码应该分开,测试代码不应该依赖实现细节或黑客来实现他们的目标,这使得他们难以维护和理解。

For trivial objects, like domain objects or DTOs or entities, you could simply serialize both instances to a string and compare that string:对于微不足道的对象,例如域对象或 DTO 或实体,您可以简单地将两个实例序列化为字符串并比较该字符串:

var object1Json = JsonConvert.SerializeObject(object1);
var object2Json = JsonConvert.SerializeObject(object2);

Assert.AreEqual(object1Json, object2Json);

This of course has various caveats, so evaluate whether for your classes, the JSON contains the expected values that should be compared.这当然有各种注意事项,因此请评估您的类,JSON 是否包含应比较的预期值。

For example, if your class contains unmanaged resources or otherwise not serializable properties, those won't be properly compared.例如,如果您的类包含非托管资源或其他不可序列化的属性,则无法正确比较它们。 It also only serializes public properties by default.默认情况下,它也只序列化公共属性。

ExpectedObjects would help you to compare equality by property value. ExpectedObjects将帮助您按属性值比较相等性。 It supports:它支持:

  1. simple object: expected.ToExpectedObject().ShouldEqual(actual);简单对象:expected.ToExpectedObject().ShouldEqual(actual);
  2. collection: expected.ToExpectedObject().ShouldEqual(actual);集合:expected.ToExpectedObject().ShouldEqual(actual);
  3. composized object: expected.ToExpectedObject().ShouldEqual(actual);组合对象:expected.ToExpectedObject().ShouldEqual(actual);
  4. partial compare: expected object need design with anonymous type, and use expected.ToExpectedObject().ShouldMatch(actual)部分比较:预期对象需要设计匿名类型,并使用 expected.ToExpectedObject().ShouldMatch(actual)

I love ExpectedObjects because of I only need to invoke 2 API for assertion of comparing object equality:我喜欢 ExpectedObjects,因为我只需要调用 2 个 API 来断言比较对象相等性:

  1. ShouldEqual()应该相等()
  2. ShouldMatch() for partial comparing ShouldMatch() 用于部分比较

I wanted a solution that didn't require adding a dependency, worked with VS unit tests, compared the field values of two objects,and told me all unequal fields.我想要一个不需要添加依赖项的解决方案,使用 VS 单元测试,比较两个对象的字段值,并告诉我所有不相等的字段。 This is what I came up with.这就是我想出的。 Note it could be extended to work with property values as well.请注意,它也可以扩展到使用属性值。

In my case, this works well for comparing the results of some file-parsing logic to ensure two technically "different" entries have fields with the same values.就我而言,这对于比较某些文件解析逻辑的结果非常有效,以确保两个技术上“不同”的条目具有具有相同值的字段。

    public class AssertHelper
    {
        public static void HasEqualFieldValues<T>(T expected, T actual)
        {
            var failures = new List<string>();
            var fields = typeof(T).GetFields(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
            foreach(var field in fields)
            {
                var v1 = field.GetValue(expected);
                var v2 = field.GetValue(actual);
                if (v1 == null && v2 == null) continue;
                if(!v1.Equals(v2)) failures.Add(string.Format("{0}: Expected:<{1}> Actual:<{2}>", field.Name, v1, v2));
            }
            if (failures.Any())
                Assert.Fail("AssertHelper.HasEqualFieldValues failed. " + Environment.NewLine+ string.Join(Environment.NewLine, failures));
        }
    }

    [TestClass]
    public class AssertHelperTests
    {
        [TestMethod]     
        [ExpectedException(typeof(AssertFailedException))]           
        public void ShouldFailForDifferentClasses()
        {            
            var actual = new NewPaymentEntry() { acct = "1" };
            var expected = new NewPaymentEntry() { acct = "2" };
            AssertHelper.HasEqualFieldValues(expected, actual);
        }
    }

Since neither have yet been mentioned on this question, there are a couple of other well adopted libraries out there that can help with this problem:由于在这个问题上都没有被提及,因此还有几个其他被广泛采用的库可以帮助解决这个问题:

  1. Fluent Assertions - see object graph comparison Fluent Assertions - 查看对象图比较

    actual.Should().BeEquivalentTo(expected);

  2. Semantic Comparison语义比较

    Likeness<MyModel, MyModel>(actual).ShouldEqual(expected);

I personally prefer Fluent Assertions as provides greater flexibility with member exclusions etc and it supports the comparison of nested objects out of the box.我个人更喜欢 Fluent Assertions,因为它在成员排除等方面提供了更大的灵活性,并且它支持开箱即用的嵌套对象的比较。

Hope this helps!希望这可以帮助!

Assert methods rely on the object's Equals and GetHashcode.断言方法依赖于对象的 Equals 和 GetHashcode。 You can implement that, but if this object equality is not needed outside unit tests I would instead consider comparing the individual primitive types on the object.您可以实现它,但如果在单元测试之外不需要此对象相等性,我会考虑比较对象上的各个原始类型。 Looks like the objects are simple enough and overriding of equals is not really warranted.看起来对象很简单,并没有真正保证重写 equals 。

If you want to compare only properties of a complex type object, Iterating over object properties will gives all the properties.如果您只想比较复杂类型对象的属性,迭代对象属性将给出所有属性。 You can try below code.你可以试试下面的代码。

//Assert
foreach(PropertyInfo property in Object1.GetType().GetProperties())
{
    Assert.AreEqual(property.GetValue(Object1), property.GetValue(Object2));
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM