Assert.AreEqual如何确定两个泛型IEnumerables之间的相等性?

36

我有一个单元测试,用于检查方法是否返回正确的 IEnumerable。该方法使用 yield return 构建可枚举对象。以下是其可枚举对象所属的类:

enum TokenType
{
    NUMBER,
    COMMAND,
    ARITHMETIC,
}

internal class Token
{
    public TokenType type { get; set; }
    public string text { get; set; }
    public static bool operator == (Token lh, Token rh) { return (lh.type == rh.type) && (lh.text == rh.text); }
    public static bool operator != (Token lh, Token rh) { return !(lh == rh); }
    public override int GetHashCode()
    {
        return text.GetHashCode() % type.GetHashCode();
    }
    public override bool Equals(object obj)
    {
        return this == (Token)obj;
    }
}

这是方法的相关部分:

 foreach (var lookup in REGEX_MAPPING)
 {
     if (lookup.re.IsMatch(s))
     {
         yield return new Token { type = lookup.type, text = s };
         break;
     }
 }

如果我将此方法的结果存储在actual中,创建另一个可枚举对象expected,并像这样进行比较...
  Assert.AreEqual(expected, actual);

如果出现了断言失败的情况,请检查以下问题。

我编写了一个扩展方法,用于 IEnumerable,类似于Python的zip函数(将两个 IEnumerables 组合成一组对),并尝试执行以下操作:

foreach(Token[] t in expected.zip(actual))
{
    Assert.AreEqual(t[0], t[1]);
}

它成功了!那么这两个 Assert.AreEqual 有什么区别呢?


1
@Jason Baker:请不要误解我的意思,您是否考虑过,您需要像这样提出问题的事实可能意味着您正在使事情过于复杂? - Mitch Wheat
2
额...不,其实不是。 :-) 你能指出我的复杂之处吗? - Jason Baker
此外,我并不认为在 GetHashCode() 方法中使用 "text.GetHashCode() % type.GetHashCode();" 作为返回值是一个好主意... - Mitch Wheat
2
@Mitch Wheat - 我也不确定...但这是另一个问题的主题。 - Jason Baker
4个回答

90

找到了:

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

不错的发现!我甚至不知道有SequenceEqual()扩展。谢谢! - jrista
3
当测试失败时,以下信息不会告诉你哪个元素不相等; 它只会告诉您测试失败了。Jerryjvl建议使用CollectionAssert机制,可以提供更丰富的失败信息。 - bacar
请确保在您的using指令中包含System.Linq。 - Chris

49

您是否考虑过使用 CollectionAssert 类来执行集合的相等比较?毕竟这正是它的设计目的。

附言:
如果要比较的“集合”是枚举类型,则最简单的方法是将它们用 'new List<T>(enumeration)' 包装起来进行比较。当然,构造新列表会产生一些开销,但在单元测试的上下文中,我希望这不会太重要吧?


CollectionAssert中的所有方法都设置为与ICollections进行比较。但我只有IEnumerables。是否有简单的方法将它们更改为ICollections? - Jason Baker
在这种情况下,我通常会快速包装一个 new List<T>(enumeration),以便进行比较。在单元测试的上下文中,开销不太可能成为问题。 - jerryjvl
6
使用 CollectionAssert 时,对两个参数都执行 .ToArray() 操作。 - MEMark

25

Assert.AreEqual会比较手头的两个对象。IEnumerable本身就是一种类型,并提供了在某些集合上进行迭代的机制,但它们并不是实际的集合。您最初的比较比较了两个IEnumerable, 这是一个有效的比较...但不是您所需要的。您需要比较两个旨在枚举的IEnumerable的内容

以下是我比较两个可枚举对象的方法:

Assert.AreEqual(t1.Count(), t2.Count());

IEnumerator<Token> e1 = t1.GetEnumerator();
IEnumerator<Token> e2 = t2.GetEnumerator();

while (e1.MoveNext() && e2.MoveNext())
{
    Assert.AreEqual(e1.Current, e2.Current);
}

我不确定上面的代码是否比你的.Zip方法更短,但它是最简单的。


这是有道理的。难道没有一种方法可以逐个比较两个IEnumerables对象,而不需要编写太多的代码吗? - Jason Baker
我会使用 while 循环。我已经在我的答案中更新了一个示例。 - jrista
我发现了另一种更简单有效的方法。虽然你解释了我的代码为什么不起作用,但我会接受这个答案的。 :-) - Jason Baker
5
一些指示:该方法涉及两次检查两个序列(一次计数,一次每个项),效率不高。此外,IEnumerator<T>需要使用“using”进行处理,因为它是IDisposable接口的实现。最后,您可以使用EqualityComparer<T>.Default.Equals(e1.Current, e2.Current)来避免装箱等操作。 - Marc Gravell
2
是的,它确实会迭代两次...但这只是一个简单的实现。 ;) 有一个更复杂的版本可以更高效地完成任务,但可读性较差。如果你要比较两个非常大的枚举,就需要使用更高效的方法,但我喜欢我的代码的清晰易懂。(然而,为了简洁起见,应该使用using语句。) - jrista

21

我认为实现你想要的相等性断言最简单和清晰的方法是结合jerryjvl的回答和MEMark在他的帖子上的评论 - 使用扩展方法与CollectionAssert.AreEqual相结合:

CollectionAssert.AreEqual(expected.ToArray(), actual.ToArray());

这比 SequenceEqual 答案建议的方式提供了更丰富的错误信息(它将告诉您找到了哪个意外的元素)。例如:

IEnumerable<string> expected = new List<string> { "a", "b" };
IEnumerable<string> actual   = new List<string> { "a", "c" }; // mismatching second element

CollectionAssert.AreEqual(expected.ToArray(), actual.ToArray());
// Helpful failure message!
//  CollectionAssert.AreEqual failed. (Element at index 1 do not match.)    

Assert.IsTrue(expected.SequenceEqual(actual));
// Mediocre failure message:
//  Assert.IsTrue failed.   

如果/当你的测试失败时,你会非常高兴你以这种方式进行测试--有时候甚至可以在不使用调试器的情况下知道出了什么问题--而且嘿,你正在正确地进行TDD,所以你首先编写失败的测试,对吧? ;-)

如果你使用AreEquivalent来测试等价性(顺序无关紧要),错误消息将变得更加有用:

CollectionAssert.AreEquivalent(expected.ToList(), actual.ToList());
// really helpful error message!
//  CollectionAssert.AreEquivalent failed. The expected collection contains 1
//  occurrence(s) of <b>. The actual collection contains 0 occurrence(s).   

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接