如何使用NUnit单元测试比较复杂对象

4
学习如何使用NUnit编写单元测试。
比较两个复杂对象时遇到困难。
这里有一个非常相似的问题的答案(使用Assert.AreEqual()比较两个对象),但是看起来你需要在你的对象上重写Equals()方法——考虑到可能存在的许多对象以及它们嵌套对象上可能存在的属性数量,这并不理想。
给出示例对象:
   public class AMockObject
   {
       public int Id { get; set; }
       public ICollection<int> Numbers { get; set; }

       public AMockObject()
       {
          Numbers = new List<int>();
       }           
    }

我希望比较两个不同实例的对象是否具有相同的值,但发现Assert.AreEqual()并没有像我预期的那样起作用。

例如,以下所有内容都无法通过测试:

// Example 1
AMockObject a = new AMockObject();
AMockObject b = new AMockObject();
Assert.AreEqual(a,b); // Fails - they're not equal

// Example 2
AMockObject a = new AMockObject() { Id = 1 };
AMockObject b = new AMockObject() { Id = 1 };
Assert.AreEqual(a, b); // Also fails

// Example 3
AMockObject a = new AMockObject() { Id = 1 };
a.Numbers.Add(1);
a.Numbers.Add(2);
a.Numbers.Add(3);    
AMockObject b = new AMockObject() { Id = 1 };
b.Numbers.Add(1);
b.Numbers.Add(2);
b.Numbers.Add(3);    
Assert.AreEqual(a, b); // also fails

我们有一些代码,其中在克隆各种对象,其中一些非常大。 考虑到这是一个很普遍的事情,在属性值级别上测试两个对象是否相同是否有同样普遍的方法?
这个示例有两个属性。在真实世界中,我有一个具有几十个属性的对象,其中一些是其他复杂对象的列表。
目前,我正在对对象进行序列化并比较字符串,但这感觉不太理想。

我认为你的序列化方法可能并不那么糟糕。否则,你将需要覆盖相等性内容,或编写一些深入的复杂反射代码。 - Derek
你需要考虑列表中的元素,1、2、3和3、1、2是否相同?顺序是否重要?XML比较可能会得到不同的结果。 - Derek
@Derek - 对于这个例子,顺序可能并不重要。虽然为了进行真正的比较,它可能很重要。在我的实际情况中,它并不重要,但这并不意味着它不重要。 - Darren Wainwright
4个回答

6

在单元测试中有一个叫做Fluent Assertions的工具,它可以进行这样的比较。

然而需要注意的是:

只要两个对象图具有相同名称和值的属性,无论这些对象的类型如何,这两个对象就是等价的。如果一种类型可以转换为另一种类型并且结果相等,则两个属性也是相等的。集合属性的类型被忽略,只要集合实现了System.Collections.IEnumerable接口,并且集合中的所有项都是结构相等的。请注意,实际行为由FluentAssertions.AssertionOptions管理的全局默认值决定。

using FluentAssertions;

//...

// Example 1
AMockObject a = new AMockObject();
AMockObject b = new AMockObject();
a.ShouldBeEquivalentTo(b); // Asserts that an object is equivalent to another object.

// Example 2
AMockObject a = new AMockObject() { Id = 1 };
AMockObject b = new AMockObject() { Id = 1 };
a.ShouldBeEquivalentTo(b); //Asserts that an object is equivalent to another object.

// Example 3
AMockObject a = new AMockObject() { Id = 1 };
a.Numbers.Add(1);
a.Numbers.Add(2);
a.Numbers.Add(3);    
AMockObject b = new AMockObject() { Id = 1 };
b.Numbers.Add(1);
b.Numbers.Add(2);
b.Numbers.Add(3);
a.ShouldBeEquivalentTo(b)    
a.Numbers.ShouldAllBeEquivalentTo(b.Numbers); // Asserts that a collection of objects is equivalent to another collection of objects.

这里是文档


这看起来非常有趣!谢谢 :) - 我还没有深入了解他们的github,不知道他们是否以某种方式序列化对象,考虑到被忽略的类型(在我的情况下不是问题)。 - Darren Wainwright
再次感谢NKosi。我们也在尝试使用BDD风格的测试用例,所以这很适合。 - Darren Wainwright

1
将其序列化为 Json 并进行比较。
使用 Newtonsoft.Json.JsonConverter,只需这样做。
var expected = JsonConvert.SerializeObject(a);
var result = JsonConvert.SerializeObject(b);

Assert.AreEqual(expected, result);

0

既然没有其他变化,你可以简单地比较创建对象的属性值吗?


这个例子有2个属性。在现实生活中,我想要比较的对象有超过20个属性,包括包含许多属性的其他对象列表 - 想象一下一个图形。 - Darren Wainwright
根据我的经验,我是这样做的(无论对象属性的数量和复杂性,也不需要修改equals和gethashvalue)。您可以断言它们是同一类的实例(如果在您的情况下有意义),然后手动检查预定义的期望答案的值。 - marto

0
你可以使用反射(参见使用反射获取字段),并迭代两个类中的字段。FieldInfo.GetValue()应该能够获取要比较的值。

我假定你已经使用了反射?这会导致写更多的代码,从而增加出错代码的概率。我宁愿不去编写一个单元测试来测试我的单元测试 :) - Darren Wainwright
我同意这并不是一件简单的事情,但这是一种制作一个“万能比较”模板的方法。看起来你需要的不是那么通用,所以这可能有些过度设计了。 - Surak of Vulcan
完全的、完美的比较是我理想中并且最终需要的。令人惊讶的是,在这些领先的测试框架中,没有一个内置了这样的功能。 - Darren Wainwright

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接