如何断言两个列表包含NUnit中具有相同公共属性的元素?

我想声明两个列表的元素包含我期望的值,如:

var foundCollection = fooManager.LoadFoo(); var expectedCollection = new List() { new Foo() { Bar = "a", Bar2 = "b" }, new Foo() { Bar = "c", Bar2 = "d" } }; //assert: I use AreEquivalent since the order does not matter CollectionAssert.AreEquivalent(expectedCollection, foundCollection); 

但是上面的代码不起作用(我猜是因为.Equals()对于具有相同值的不同对象不返回true)。 在我的测试中,我只关心公共属性值,而不关心对象是否相等。 我该怎么做才能做出我的断言?

重新回答

有一个CollectionAssert.AreEqual(IEnumerable, IEnumerable, IComparer)重载断言两个集合以相同的顺序包含相同的对象,使用IComparer实现来检查对象的等价性。

在上述场景中,顺序并不重要。 但是,为了充分处理两个集合中存在多个等效对象的情况,有必要首先对每个集合中的对象进行排序并使用逐个比较来确保等效对象的数量也相同在两个集合中。

Enumerable.OrderBy提供了一个带有IComparer参数的重载。 为确保两个集合按相同顺序排序,标识属性的类型或多或少要求实现IComparable 。 下面是一个比较器类的示例,它实现了IComparerIComparer接口,并假设Bar在排序时优先:

 public class FooComparer : IComparer, IComparer { public int Compare(object x, object y) { var lhs = x as Foo; var rhs = y as Foo; if (lhs == null || rhs == null) throw new InvalidOperationException(); return Compare(lhs, rhs); } public int Compare(Foo x, Foo y) { int temp; return (temp = x.Bar.CompareTo(y.Bar)) != 0 ? temp : x.Bar2.CompareTo(y.Bar2); } } 

要断言两个集合中的对象是相同的并且数量相同(但不一定以相同的顺序开头),以下行应该可以解决问题:

 var comparer = new FooComparer(); CollectionAssert.AreEqual( expectedCollection.OrderBy(foo => foo, comparer), foundCollection.OrderBy(foo => foo, comparer), comparer); 

不,NUnit没有当前状态的机制。 你必须推出自己的断言逻辑。 作为单独的方法,或使用Has.All.Matches

 Assert.That(found, Has.All.Matches(f => IsInExpected(f, expected))); private bool IsInExpected(Foo item, IEnumerable expected) { var matchedItem = expected.FirstOrDefault(f => f.Bar1 == item.Bar1 && f.Bar2 == item.Bar2 && f.Bar3 == item.Bar3 ); return matchedItem != null; } 

这当然假设您事先知道所有相关属性(否则, IsInExpected将不得不求助于reflection)并且元素顺序不相关。

(并且您的假设是正确的,NUnit的集合断言使用类型的默认比较器,在大多数情况下,用户定义的类型将是对象的ReferenceEquals

使用Has.All.Matches()可以很好地将找到的集合与预期的集合进行比较。 但是,没有必要将Has.All.Matches()使用的谓词定义为单独的函数。 对于相对简单的比较,谓词可以作为lambda表达式的一部分包含在内。

 Assert.That(found, Has.All.Matches(f => expected.Any(e => f.Bar1 == e.Bar1 && f.Bar2 == e.Bar2 && f.Bar3= = e.Bar3))); 

现在,虽然这个断言将确保找到的集合中的每个条目也存在于预期的集合中,但它并不能certificate相反,即预期集合中的每个条目都包含在找到的集合中。 因此,当重要的是要知道找到期望的包含在语义上是等价的(即,它们包含相同的语义上等效的条目),我们必须添加一个额外的断言。

最简单的选择是添加以下内容。

 Assert.AreEqual(found.Count() == expected.Count()); 

对于那些喜欢更大锤子的人来说,可以使用以下断言。

 Assert.That(expected, Has.All.Matches(e => found.Any(f => e.Bar1 == f.Bar1 && e.Bar2 == f.Bar2 && e.Bar3= = f.Bar3))); 

通过将上面的第一个断言与第二个(首选)或第三个断言结合使用,我们现在已经certificate两个集合在语义上是相同的。

你试过像这样的吗?

 Assert.That(foundCollection, Is.EquivalentTo(expectedCollection)) 

要对复杂类型执行等效操作,您需要实现IComaprable。

http://support.microsoft.com/kb/320727

或者,您可以使用递归reflection,这是不太理想的。

一种选择是编写自定义约束来比较项目。 这是一篇关于这个主题的好文章: http : //www.davidarno.org/2012/07/25/improving-nunit-custom-constraints-with-syntax-helpers/

我有类似的问题。 列出贡献者,其中包含“评论者”和其他人…我希望获得所有评论,并从中获得创作者,但我只对独特的创作者感兴趣。 如果有人创建了50条评论,我只希望她的名字出现一次。 所以我写了一个测试,看看评论者是否在GetContributors()结果中。

我可能错了,但是我认为你之后(当我发现这篇文章时我所追求的)是断言在一个集合中的每个项目中只有一个,在另一个集合中找到。

我解决了这个问题:

 Assert.IsTrue(commenters.All(c => actual.Count(p => p.Id == c.Id) == 1)); 

如果您还希望结果列表不包含除预期之外的其他项目,您也可以只比较列表的长度。

 Assert.IsTrue(commenters.length == actual.Count()); 

我希望这是有帮助的,如果是的话,如果你评价我的答案,我将非常感激。

我建议不要使用reflection或任何复杂的东西,它只是增加了更多的工作/维护。

序列化对象(我推荐json)和字符串比较它们。 我不确定你为什么反对订购,但我仍然推荐它,因为它会为每种类型保存自定义比较。

它自动适用于域对象的更改。

示例(SharpTestsEx流利)

 using Newtonsoft.Json; using SharpTestsEx; JsonConvert.SerializeObject(actual).Should().Be.EqualTo(JsonConvert.SerializeObject(expected)); 

您可以将其编写为简单的扩展,使其更具可读性。

  public static class CollectionAssertExtensions { public static void CollectionAreEqual(this IEnumerable actual, IEnumerable expected) { JsonConvert.SerializeObject(actual).Should().Be.EqualTo(JsonConvert.SerializeObject(expected)); } } 

然后使用你的例子调用它如下:

 var foundCollection = fooManager.LoadFoo(); var expectedCollection = new List() { new Foo() { Bar = "a", Bar2 = "b" }, new Foo() { Bar = "c", Bar2 = "d" } }; foundCollection.CollectionAreEqual(foundCollection); 

你会得到一个这样的断言消息:

…: “一”, “BAR2”: “B”},{ “酒吧”: “d”, “BAR2”: “d”}]

…: “一”, “BAR2”: “B”},{ “酒吧”: “C”, “BAR2”: “d”}]

_ __ _ __ _ _ _ _ _ _ _ _ _ _ _ _ ^ _ _ _ _ _

简单的代码解释了如何使用IComparer

 using System.Collections; using System.Collections.Generic; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace CollectionAssert { [TestClass] public class UnitTest1 { [TestMethod] public void TestMethod1() { IComparer collectionComparer = new CollectionComparer(); var expected = new List{ new SomeModel { Name = "SomeOne", Age = 40}, new SomeModel{Name="SomeOther", Age = 50}}; var actual = new List { new SomeModel { Name = "SomeOne", Age = 40 }, new SomeModel { Name = "SomeOther", Age = 50 } }; NUnit.Framework.CollectionAssert.AreEqual(expected, actual, collectionComparer); } } public class SomeModel { public string Name { get; set; } public int Age { get; set; } } public class CollectionComparer : IComparer, IComparer { public int Compare(SomeModel x, SomeModel y) { if(x == null || y == null) return -1; return x.Age == y.Age && x.Name == y.Name ? 0 : -1; } public int Compare(object x, object y) { var modelX = x as SomeModel; var modelY = y as SomeModel; return Compare(modelX, modelY); } } }