Assert.AreEqual如何确定两个通用IEnumebles之间的相等性?

Jas*_*ker 35 c# ienumerable unit-testing assert mstest

我有一个单元测试来检查方法是否返回正确的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;
    }
}
Run Code Online (Sandbox Code Playgroud)

这是该方法的相关部分:

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

如果我将此方法的结果存储在中actual,则使另一个可枚举expected,并将它们比作这样...

  Assert.AreEqual(expected, actual);
Run Code Online (Sandbox Code Playgroud)

......,断言失败了.

我写了一个IEnumerable类似于Python zip函数的扩展方法(它将两个IEnumerables组合成一组对)并尝试了这个:

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

有效!那么这两个Assert.AreEquals有什么区别?

Jas*_*ker 88

找到了:

Assert.IsTrue(expected.SequenceEqual(actual));
Run Code Online (Sandbox Code Playgroud)

  • 当测试失败时,这不会告诉您有关哪个元素不相等的任何信息; 它会告诉你它失败了.jerryjvl建议的CollectionAssert机制提供了更丰富的失败信息. (3认同)

jer*_*jvl 48

您是否考虑过使用CollectionAssert该类...考虑到它是否打算对集合执行相等检查?

附录:
如果被比较的'集合'是枚举,那么简单地用' new List<T>(enumeration)' 包装它们是进行比较的最简单方法.构建一个新的列表当然会产生一些开销,但在单元测试的背景下,我希望这不应该太重要吗?

  • 使用CollectionAssert时,只需对两个参数执行.ToArray(). (6认同)

jri*_*sta 25

Assert.AreEqual将要比较手头的两个对象.IEnumerables本身就是类型,并提供迭代某些集合的机制......但它们实际上并不是那个集合.你的原始比较比较了两个IEnumerables,这是一个有效的比较...但不是你需要的.您需要比较两者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);
}
Run Code Online (Sandbox Code Playgroud)

我不确定上面的代码是否比你的.Zip方法少,但它就像它得到的一样简单.

  • 一些指针:该方法涉及两次检查两个序列(一次计数,每个项目一次) - 效率不高.另外,IEnumerator <T>是IDisposable,因此应该涉及"使用".最后,您可以使用EqualityComparer <T> .Default.Equals(e1.Current,e2.Current)来避免装箱等. (5认同)
  • 是的,它会迭代两次......但这是一个简单的实现.;)有一个更复杂的版本,可以更有效地完成工作,但不太清晰.如果你比较两个非常大的枚举,将需要一个更有效的方法,但我喜欢我的清晰度.(但是,确实应该使用陈述......为简洁而省略.) (2认同)

bac*_*car 19

我认为断言你想要的平等的最简单和最清晰的方式是jerryjvl的回答和MEMark对他的帖子的评论 - 结合CollectionAssert.AreEqual扩展方法:

CollectionAssert.AreEqual(expected.ToArray(), actual.ToArray());
Run Code Online (Sandbox Code Playgroud)

这提供了比OP建议的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.   
Run Code Online (Sandbox Code Playgroud)

如果/当你的测试失败时,你会非常高兴你这样做 - 有时你甚至可以知道什么是错的而不必打破调试器 - 嘿,你正在做正确的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).   
Run Code Online (Sandbox Code Playgroud)