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

Lou*_*hys 19 .net c# nunit unit-testing assertion

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

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);
Run Code Online (Sandbox Code Playgroud)

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

And*_*son 21

重新回答

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

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

Enumerable.OrderBy提供了一个带IComparer<T>参数的重载.为确保以相同的顺序对两个集合进行排序,或多或少地要求标识属性的类型实现IComparable.下面是一个比较器类的示例,它实现了IComparerIComparer<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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

var comparer = new FooComparer();
CollectionAssert.AreEqual(
    expectedCollection.OrderBy(foo => foo, comparer), 
    foundCollection.OrderBy(foo => foo, comparer), comparer);    
Run Code Online (Sandbox Code Playgroud)

  • @LouisRhys我添加了示例代码,其中两个集合中对象的顺序无关紧要. (2认同)

k.m*_*k.m 6

不,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;
}
Run Code Online (Sandbox Code Playgroud)

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

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


小智 6

使用 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)));
Run Code Online (Sandbox Code Playgroud)

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

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

Assert.AreEqual(found.Count() == expected.Count());
Run Code Online (Sandbox Code Playgroud)

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

Assert.That(expected, Has.All.Matches<Foo>(e => 
    found.Any(f =>
        e.Bar1 == f.Bar1 &&
        e.Bar2 == f.Bar2 &&
        e.Bar3= = f.Bar3)));
Run Code Online (Sandbox Code Playgroud)

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