xunit.net 中是否有一种简单的方法可以在不考虑项目顺序的情况下比较两个集合?

Is there an easy way in xunit.net to compare two collections without regarding the items' order?

在我的一个测试中,我想确保一个集合中有特定的项目。因此,我想将此集合与预期集合的项目进行比较 ,而不考虑项目的顺序 。目前,我的测试代码看起来有点像这样:

[Fact]
public void SomeTest()
{
    // Do something in Arrange and Act phase to obtain a collection
    List<int> actual = ...

    // Now the important stuff in the Assert phase
    var expected = new List<int> { 42, 87, 30 };
    Assert.Equal(expected.Count, actual.Count);
    foreach (var item in actual)
        Assert.True(expected.Contains(item));
}

在 xunit.net 中是否有更简单的方法来实现此目的?我不能使用 Assert.Equal,因为此方法会检查两个集合中项目的顺序是否相同。我查看了 Assert.Collection 但这并没有删除上面代码中的 Assert.Equal(expected.Count, actual.Count) 语句。

不是 Xunit,而是 Linq 答案:

bool areSame = !expected.Except(actual).Any() && expected.Count == actual.Count;

所以在 XUnit 中:

Assert.True(!expected.Except(actual).Any() && expected.Count == actual.Count));

正如@robi-y所说,在Microsoft.VisualStudio.QualityTools.UnitTestFramework中有CollectionAssert.AreEquivalent

也许另一种方式是:

Assert.True(expected.SequenceEqual(actual));

这也会检查顺序。这是内部发生的事情:

using (IEnumerator<TSource> e1 = first.GetEnumerator())
using (IEnumerator<TSource> e2 = second.GetEnumerator())
{
    while (e1.MoveNext())
    {
        if (!(e2.MoveNext() && comparer.Equals(e1.Current, e2.Current))) return false;
    }
    if (e2.MoveNext()) return false;
}
return true;

因此,如果您不关心顺序,只需在两个列表之前排序即可:

Assert.True(expected.OrderBy(i => i).SequenceEqual(actual.OrderBy(i => i)));

来自 xunit.net 的 Brad Wilson 在这篇 Github Issue 中告诉我应该使用 LINQ 的 OrderBy 运算符,然后 Assert.Equal 来验证两个集合是否包含相同的项目而不考虑他们的订单。当然,你必须在相应的项目 class 上有一个 属性,你可以首先使用它来订购(我的情况下并没有)。

就我个人而言,我使用 FluentAssertions, a library that provides a lot of assertion methods that can be applied in a fluent style. Of course, there are also a lot of methods that you can use to validate collections.

解决了这个问题

在我的问题的上下文中,我会使用类似下面的代码:

[Fact]
public void Foo()
{
    var first = new[] { 1, 2, 3 };
    var second = new[] { 3, 2, 1 };

    first.Should().BeEquivalentTo(second);
}

此测试通过,因为 BeEquivalentTo 调用忽略了项目的顺序。

如果您不想使用 FluentAssertions,

Shouldly 也是一个不错的选择。

您可以使用 Microsoft

中的 CollectionAssert.AreEquivalent
CollectionAssert.AreEquivalent(expected, actual);

这与您的代码几乎相同。唯一的简化是使用 Assert.Contains 而不是 Assert.True(expected.Contains(...)).

[Fact]
public void SomeTest()
{
    // Do something in Arrange and Act phase to obtain a collection
    List<int> actual = ...

    // Now the important stuff in the Assert phase
    var expected = new List<int> { 42, 87, 30 };
    Assert.Equal(expected.Count, actual.Count);
    foreach (var item in expected)
        Assert.Contains(item, actual);
}

如果您 collection 中的项目保证是唯一的,您可以使用 HashSet。那是因为 HashSet 本质上是无序的。

[Fact]
public void Foo()
{
    var expected = new HashSet<int> { 1, 2 ,3 };
    var actual = new HashSet<int> { 3, 2, 1 };

    Assert.Equal(expected, actual);
}

这是可行的,因为 xUnit 使用 ISet.SetEquals() 方法。

This method ignores the order of elements and any duplicate elements in other.

如果 actual collection 只是一个普通的 collection(不是 HashSet)那么你仍然可以自己使用 SetEquals() 但你必须意识到重复项将被忽略。

[Fact]
public void Foo()
{
    var expected = new HashSet<int> { 1, 2 ,3 };
    var actual = new [] { 3, 2, 1, 1, 1 };

    // This also passes, but may not be what you want
    Assert.True(expected.SetEquals(actual));
}