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

时间:2012-08-29 05:27:55

标签: c# .net unit-testing nunit 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);

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

10 个答案:

答案 0 :(得分:20)

REWORKED ANSWER

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

答案 1 :(得分: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;
}

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

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

答案 2 :(得分:4)

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

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

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

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

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

答案 3 :(得分:3)

你尝试过这样的事吗?

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

答案 4 :(得分:1)

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

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

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

答案 5 :(得分:1)

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

答案 6 :(得分:1)

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

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

我解决了这个问题:

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

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

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

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

答案 7 :(得分:0)

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

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

它会自动与域对象一起更改。

示例(SharpTestsEx为流利)

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

你会收到一条断言信息:

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

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

... <强> _ __ _ __ _ __ _ __ _ < / EM> __ _ __ ^ _ __ _ _

答案 8 :(得分: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);
        }
    }
}

答案 9 :(得分:0)

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

AssertArrayEqualsByElements(list1.ToArray(), list2.ToArray());