Assert.Equal() 在将 IEnumerable(使用 yield return)与自身进行比较时失败

Assert.Equal() fails when comparing IEnumerable (using yield return) with itself

运行 在为我的代码编写单元测试时遇到以下问题。为什么在将 IEnumerable 与其自身进行比较时 Assert.Equal() 会失败?

private class ReferenceType { }

[Fact]
public void EnumerableEqualityTest()
{
    IEnumerable<ReferenceType> GetEnumerable()
    {
        yield return new ReferenceType();
    }

    var enumerable = GetEnumerable();

    Assert.Equal(enumerable, enumerable); // fails
}

要了解正在发生的事情,我们需要了解 Assert.Equal() 实际在做什么。根据 Assert.Equal<T>(IEnumerable<T> expected, IEnumerable<T> actual) 的文档,它 "Verifies that two sequences are equivalent, using a default comparer".

本例中的 Assert.Equal() 迭代可枚举以检查各个值是否相等。这意味着枚举被迭代两次以进行比较,并且每次都会创建一个 ReferenceType 的新实例(通过 yield return)。测试失败,因为引用类型的默认比较器仅检查实例是否引用同一对象。

至少有三种方法可以获得预期的结果:

  1. 使用参数为 IEqualityComparer<T>Assert.Equal() 的重载。
  2. 覆盖 ReferenceTypeEquals() 方法。
  3. 跳过 yield return 并使用实现 IEnumerable 的集合。

可以说第一个解决方案是最好的,因为它不会改变 GetEnumerable()ReferenceType 的实现。在这种 GetEnumerable() 仅在测试中使用的情况下,我会选择第三个选项,因为它最容易做到。它可能看起来像这样:

IEnumerable<ReferenceType> GetData()
{
    return new[] { new ReferenceType() };
}

或者这个:

IEnumerable<ReferenceType> GetData()
{
    var referenceTypes = new List<ReferenceType>();
    // ... add reference types
    return referenceTypes;
}

这是有效的,因为我们现在正在迭代我们获得可枚举对象时创建的集合,而不是为每次迭代创建新实例。

我认为,您可以使用以下功能来解决问题。

public bool AreEquivalentEnumerator(IEnumerator<TSource> first, IEnumerator<TSource> second)
        {
            while (first.MoveNext())
            {
                if (!(second.MoveNext() && Equals(first.Current, second.Current))) return false;
            }

            if (second.MoveNext()) return false;

            return true;
        }