21

我想断言两个列表的元素包含我期望的值,例如:

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 )。在我的测试中,我只关心公共属性值,而不关心对象是否相等。我能做些什么来做出我的主张?

4

10 回答 10

22

重新设计的答案

有一个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);
    }
}

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

var comparer = new FooComparer();
CollectionAssert.AreEqual(
    expectedCollection.OrderBy(foo => foo, comparer), 
    foundCollection.OrderBy(foo => foo, comparer), comparer);    
于 2012-08-29T08:19:20.820 回答
7

不,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 的集合断言使用类型的默认比较器,在大多数情况下,用户定义的比较器将是 object's ReferenceEquals

于 2012-08-29T07:47:15.037 回答
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)));

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

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

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

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

于 2014-04-22T21:30:50.300 回答
3

你有没有尝试过这样的事情?

Assert.That(expectedCollection, Is.EquivalentTo(foundCollection))
于 2012-08-29T06:40:41.987 回答
2

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

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

我这样解决了这个问题:

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

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

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

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

于 2014-10-15T11:47:54.950 回答
1

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

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

或者,您可以使用不太理想的递归反射。

于 2012-08-29T06:10:36.420 回答
1

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

于 2012-08-29T07:53:36.347 回答
0

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

序列化对象(我推荐 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"}]

... _ __ _ __ _ __ _ __ _ __ _ __^ _ __ _ _

于 2014-02-10T12:30:15.200 回答
0

解释如何使用 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 SomeModel{Name="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);
        }
    }
}
于 2018-04-10T01:27:16.810 回答
0

Assertion这使用程序集中的 NUnit类解决了我的问题NUnitCore

AssertArrayEqualsByElements(list1.ToArray(), list2.ToArray());
于 2020-04-20T01:54:13.420 回答