如何断言两个列表包含 NUnit 中具有相同公共属性的元素?
Posted
技术标签:
【中文标题】如何断言两个列表包含 NUnit 中具有相同公共属性的元素?【英文标题】:How to assert that two list contains elements with the same public properties in NUnit? 【发布时间】:2012-08-23 16:16:44 【问题描述】:我想断言两个列表的元素包含我期望的值,例如:
var foundCollection = fooManager.LoadFoo();
var expectedCollection = new List<Foo>()
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)。在我的测试中,我只关心公共属性值,而不关心对象是否相等。我能做些什么来表达我的主张?
【问题讨论】:
【参考方案1】:要对复杂类型执行等价操作,您需要实现 ICMaprable。
http://support.microsoft.com/kb/320727
您也可以使用递归反射,这不太理想。
【讨论】:
你的意思是我必须修改生产代码来实现这个IComparable?是否有不需要修改生产代码的解决方案,例如使用反射或指定我自己的 NUnit 比较器?这只是测试所需要的,对象本身没有可比性 然后作为我的第二个建议,使用反射遍历属性列表并生成值哈希。或者,如果对象是可序列化的,则 JSON 序列化它们并使用字符串比较 如何简单的“JSON序列化”?【参考方案2】:你尝试过这样的事情吗?
Assert.That(expectedCollection, Is.EquivalentTo(foundCollection))
【讨论】:
这与 CollectionAssert.AreEquivalent 有什么不同?无论如何,两者都不起作用,返回关于对象不相等的类似异常 我认为它与自定义 Foo 对象有关,它不知道如何比较它们,所以在这种情况下,自定义约束可能是解决方案。 是的,这确实是我的怀疑。知道如何创建自定义约束或自定义断言吗?【参考方案3】:不,NUnit 在当前状态下没有这样的机制。您必须推出自己的断言逻辑。或者作为单独的方法,或者使用Has.All.Matches
:
Assert.That(found, Has.All.Matches<Foo>(f => IsInExpected(f, expected)));
private bool IsInExpected(Foo item, IEnumerable<Foo> expected)
var matchedItem = expected.FirstOrDefault(f =>
f.Bar1 == item.Bar1 &&
f.Bar2 == item.Bar2 &&
f.Bar3 == item.Bar3
);
return matchedItem != null;
这当然假设您预先知道所有相关属性(否则,IsInExpected
将不得不求助于反射)并且元素顺序不相关。
(你的假设是正确的,NUnit 的集合断言使用默认的类型比较器,在大多数情况下,用户定义的比较器将是对象的 ReferenceEquals
)
【讨论】:
【参考方案4】:一种选择是编写自定义约束来比较项目。这是一篇关于这个主题的好文章:http://www.davidarno.org/2012/07/25/improving-nunit-custom-constraints-with-syntax-helpers/
【讨论】:
【参考方案5】:重新设计的答案
有一个CollectionAssert.AreEqual(IEnumerable, IEnumerable, IComparer)
重载来断言两个集合以相同的顺序包含相同的对象,使用IComparer
实现来检查对象等价性。
在上述场景中,顺序并不重要。但是,为了充分处理两个集合中存在多个等价对象的情况,有必要首先对每个集合中的对象进行排序,并使用一对一比较来确保等价对象的数量也相同在两个集合中。
Enumerable.OrderBy
提供了一个采用IComparer<T>
参数的重载。为了确保两个集合按相同的顺序排序,或多或少需要标识属性的类型实现IComparable
。下面是一个比较器类的示例,它同时实现了IComparer
和IComparer<Foo>
接口,并且假设在排序时Bar
优先:
public class FooComparer : IComparer, IComparer<Foo>
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);
【讨论】:
实际上,我不想断言订单..关于如何编写帮助方法的任何想法? @LouisRhys 我添加了示例代码,其中两个集合中对象的顺序无关紧要。 如果列表长度不同,使用上述 Any() 会产生问题。如果预期是实际的子集,则测试将通过。例如,预期 = A, B,实际 = A,C,B A,B.Except(A,B,C = 为了允许不同的长度,可以添加对计数的检查或运行两个方向的例外。 @AlanT 你完全正确,很抱歉疏忽。我已经相应地更新了答案。 @Louis Rhys 如果实际或预期中存在重复项目,则会出现问题。使用的集合操作不允许给定项目的倍数。如果可能发生重复,则可以使用 'lhsCount == rhsCount && lhs.Intersect(rhs, equalComparer).Count() == lhsCount;' 比较列表。【参考方案6】:我建议不要使用反射或任何复杂的东西,它只会增加更多的工作/维护。
序列化对象(我推荐 json)和字符串比较它们。 我不确定您为什么反对订购,但我仍然推荐它,因为它会为每种类型保存自定义比较。
它会自动处理领域对象的变化。
示例(SharpTestsEx for fluent)
using Newtonsoft.Json;
using SharpTestsEx;
JsonConvert.SerializeObject(actual).Should().Be.EqualTo(JsonConvert.SerializeObject(expected));
你可以把它写成一个简单的扩展,让它更具可读性。
public static class CollectionAssertExtensions
public static void CollectionAreEqual<T>(this IEnumerable<T> actual, IEnumerable<T> expected)
JsonConvert.SerializeObject(actual).Should().Be.EqualTo(JsonConvert.SerializeObject(expected));
然后使用您的示例调用它:
var foundCollection = fooManager.LoadFoo();
var expectedCollection = new List<Foo>()
new Foo() Bar = "a", Bar2 = "b" ,
new Foo() Bar = "c", Bar2 = "d"
;
foundCollection.CollectionAreEqual(foundCollection);
你会收到这样的断言消息:
...:"a","Bar2":"b","Bar":"d","Bar2":"d"]
...:"a","Bar2":"b","Bar":"c","Bar2":"d"]
...__________________^_____
【讨论】:
【参考方案7】:使用 Has.All.Matches() 非常适合将 找到 集合与 预期 集合进行比较。但是,不必将 Has.All.Matches() 使用的谓词定义为单独的函数。对于相对简单的比较,谓词可以像这样包含在 lambda 表达式中。
Assert.That(found, Has.All.Matches<Foo>(f =>
expected.Any(e =>
f.Bar1 == e.Bar1 &&
f.Bar2 == e.Bar2 &&
f.Bar3 == e.Bar3)));
现在,虽然此断言将确保 found 集合中的每个条目也存在于 expected 集合中,但它并不能证明相反,即预期集合包含在 found 集合中。因此,当知道 found 和 expected 包含在语义上是等价的(即它们包含相同的语义上等价的条目)很重要时,我们必须添加一个额外的断言。
最简单的选择是添加以下内容。
Assert.AreEqual(found.Count(), expected.Count());
对于那些喜欢更大的锤子的人,可以使用以下断言。
Assert.That(expected, Has.All.Matches<Foo>(e =>
found.Any(f =>
e.Bar1 == f.Bar1 &&
e.Bar2 == f.Bar2 &&
e.Bar3 == f.Bar3)));
通过将上面的第一个断言与第二个(首选)或第三个断言结合使用,我们现在已经证明这两个集合在语义上是相同的。
【讨论】:
【参考方案8】:我遇到了类似的问题。列出贡献者,其中包含“评论者”和其他人......我想获得所有的 cmets 并从中派生创作者,但我只对独特的创作者感兴趣。如果有人创建了 50 cmets,我只希望她的名字出现一次。所以我写了一个测试,看看评论者是否在 GetContributors() 结果中。
我可能是错的,但我认为您的后继(我发现这篇文章时的后继)是断言在一个集合中的每个项目中恰好有一个,在另一个集合中找到。
我是这样解决的:
Assert.IsTrue(commenters.All(c => actual.Count(p => p.Id == c.Id) == 1));
如果您还希望结果列表不包含预期之外的其他项目,您也可以比较列表的长度..
Assert.IsTrue(commenters.length == actual.Count());
我希望这会有所帮助,如果是这样,如果您能评价我的回答,我将不胜感激。
【讨论】:
【参考方案9】:解释如何使用 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<SomeModel> new SomeModel Name = "SomeOne", Age = 40, new SomeModelName="SomeOther", Age = 50;
var actual = new List<SomeModel> 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<SomeModel>
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);
【讨论】:
【参考方案10】:这解决了我使用来自 NUnitCore
程序集的 NUnit 的 Assertion
类的问题:
AssertArrayEqualsByElements(list1.ToArray(), list2.ToArray());
【讨论】:
以上是关于如何断言两个列表包含 NUnit 中具有相同公共属性的元素?的主要内容,如果未能解决你的问题,请参考以下文章