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

问题描述 投票:0回答:9

使用 Nunit 或 Microsoft.VisualStudio.TestTools.UnitTesting。现在我的断言失败了。

    [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);
    }
c# .net unit-testing tdd nunit
9个回答
28
投票

您有两个不同的

Board
实例,因此您对
Assert.AreEqual
的调用将会失败。即使它们的整个内容看起来相同,您也是在比较参考,而不是基础值。

您必须指定是什么使两个

Board
实例相等。

你可以在测试中做到这一点:

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...

或者你可以在课堂上进行:(注意我是即时写的 - 你需要调整它)

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
    }
}

26
投票

我曾经重写 getHasCode 和 equals,但我从来不喜欢它,因为我不想为了单元测试而更改我的生产代码。 还有就是有点痛。

然后我转向了太多的反思来比较侵入性较小的对象......但这需要大量的工作(很多极端情况)

最后我用:

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

更新,6年后

我现在使用更通用的 .NET 库 Fluentassertions 它的作用与上面相同,但具有更多功能和漂亮的 DSL,具体替换为:https://fluidassertions.com/objectgraphs/

PM> Install-Package FluentAssertions

而且经过几年的经验,我仍然不推荐覆盖路线,我什至认为这是一个不好的做法。如果您不小心,在使用某些集合(例如字典)时可能会引入性能问题。此外,当您有一个真正的业务案例来重载这些方法时,您就会遇到麻烦,因为您已经有这个测试代码了。生产代码和测试代码应该分开,测试代码不应该依赖实现细节或黑客来实现其目标,这使得它们难以维护和理解。


21
投票

对于简单的对象,例如域对象或 DTO 或实体,您可以简单地将两个实例序列化为字符串并比较该字符串:

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

Assert.AreEqual(object1Json, object2Json);

这当然有各种警告,因此请评估您的类中的 JSON 是否包含应比较的预期值。

例如,如果您的类包含非托管资源或不可序列化的属性,则将无法正确比较这些资源。默认情况下它也只序列化公共属性。


8
投票

ExpectedObjects 将帮助您通过属性值比较相等性。它支持:

  1. 简单对象:expected.ToExpectedObject().ShouldEqual(actual);
  2. 集合:expected.ToExpectedObject().ShouldEqual(实际);
  3. 组合对象:expected.ToExpectedObject().ShouldEqual(actual);
  4. 部分比较:预期对象需要设计为匿名类型,并使用expected.ToExpectedObject().ShouldMatch(actual)

我喜欢 ExpectedObjects,因为我只需要调用 2 个 API 来断言比较对象相等性:

  1. 应该等于()
  2. ShouldMatch() 用于部分比较

7
投票

我想要一个不需要添加依赖项的解决方案,可以使用 VS 单元测试,比较两个对象的字段值,并告诉我所有不相等的字段。这就是我想出来的。请注意,它也可以扩展到使用属性值。

就我而言,这对于比较某些文件解析逻辑的结果非常有效,以确保两个技术上“不同”的条目具有具有相同值的字段。

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

7
投票

由于这个问题还没有被提及,所以还有一些其他被广泛采用的库可以帮助解决这个问题:

  1. 流畅的断言 - 请参阅对象图比较

    actual.Should().BeEquivalentTo(expected);

  2. 语义比较

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

我个人更喜欢 Fluent Assertions,因为它在成员排除等方面提供了更大的灵活性,并且它支持开箱即用的嵌套对象的比较。

希望这有帮助!


2
投票

Assert 方法依赖于对象的 Equals 和 GetHashcode。您可以实现它,但如果在单元测试之外不需要这种对象相等性,我会考虑比较对象上的各个基元类型。 看起来对象足够简单并且重写 equals 并没有真正的必要。


0
投票

如果您只想比较复杂类型对象的属性, 迭代对象属性将给出所有属性。 您可以尝试下面的代码。

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

0
投票

使用 Net6+,您可以使用 IEqualityComparer。您可以在顶部添加类似的方法,而不必重写类本身。如果您想要更深入的检查,您可能会查找如何处理 GetHashCode

Assert.AreEqual(expected.Rows.Count, actual.Rows.Count, new BoardEqualityComparerMethod());

public class BoardEqualityComparerMethod : IEqualityComparer<Board>
{
    public bool Equals(Board x, Board y)
    {
        if (x == null)
            return y == null;
        else if (y == null)
            return false;
        else
            //Do a check here using Foreach or creating it's own IEqualityComparer
            return x.Rows == y.Rows;
    }

    public int GetHashCode(Board obj)
    {
        //do hashing if needed
        return obj.GetHashCode();
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.