【问题标题】:CollectionAssert.AreEqual passes but CollectionAssert.AreEquivalent Fails?CollectionAssert.AreEqual 通过但 CollectionAssert.AreEquivalent 失败?
【发布时间】:2013-04-06 09:00:47
【问题描述】:

我已经构建了一些复杂的对象,我正在尝试通过进行一些单元测试来验证它是否正常工作。这涉及比较一些 List(Of T),所以我尝试使用 CollectionAssert。 现在我遇到了一些奇怪的事情。

首先我使用 CollectionAssert.AreEqual 查看第一个列表是否相等。这个断言通过了。但是为了简单起见,我想使用 CollectionAssert.AreEqual,这样我就不必以正确的顺序创建预期的对象,所以我开始尝试。使用完全相同的代码,CollectionAssert.AreEquivalent 失败。我会说这很奇怪,因为等价是比等价更宽松的断言,对吧?我收到此错误:

CollectionAssert.AreEquivalent failed. The expected collection contains 1 occurrence(s) of <MyObject>. The actual collection contains 0 occurrence(s).

我尝试过调试,但我无法调试 .Net 框架,即使我设置了下载符号文件。所以我只能看到它进入我的自定义 Equals 函数一次 - 返回 true - 然后断言失败。两个对象都有两个元素。调用栈是(倒序):

  • CollectionAssert.AreEquivalent
  • CollectionAssert.AreEquivalent(重载)
  • CollectionAssert.FindMisMatchedElement
  • Generic.Dictionary(Of Object, int).TryGetValue
  • Generic.Dictionary(Of Object, int).FindEntry
  • Generic.ObjectEqualityComparer.Equals
  • 我的自定义 Equals

现在我正在写这篇文章,一个想法出现了,我看到了一个潜在的问题。我看到它在内部使用字典。哪个可能充当某种哈希图,其中 int 是实际列表中的索引?这是否意味着我需要实现自定义 IEqualityComparer,而不是覆盖 equals?那么我的 getHashCode() 应该如何? (我猜这很关键,因为我认为它可能被用作字典中的键?)

【问题讨论】:

  • 您是否有可能在课堂上覆盖了Equals,但没有 GetHashCode
  • 如果您显示您的代码,这将更容易回答。
  • 我明白,但在这种情况下,它是相当多的代码。正如我在上一段中提到的,我确实没有覆盖 GetHashCode。但是我还不太明白,我的 GetHashCode 应该是什么样子。
  • 类T基本上包含4个Strings和一个Enum类型。
  • 我不太明白它需要 GetHashCode 做什么。正如我所写,我认为它可能用于该字典中的键。但是,哈希码可能会发生冲突,对吧?此外,我的 equals 函数中有一些逻辑,它不是直接比较所有字符串 1 对 1,所以这意味着我基本上会实现这个功能两次,以确保相等的对象获得相同的哈希码,对吗?这感觉很奇怪......

标签: vb.net unit-testing assert


【解决方案1】:

您走在正确的轨道上:问题确实是您在覆盖Equals 时没有覆盖GetHashCode

这是重现您的问题的示例:

void Main()
{
    var a = new []{new Broken{Foo="a"}, new Broken{Foo="b"}};
    var b = new []{new Broken{Foo="a"}, new Broken{Foo="b"}};

    CollectionAssert.AreEqual(a, b);
    CollectionAssert.AreEquivalent(a, b);
}

class Broken
{
    public string Foo {get;set;}

    public override bool Equals(object obj)
    {
        return Foo == ((Broken)obj).Foo;
    }
}

正如您正确指出的,CollectionAssert.AreEquivalent 使用Dictionary,它用于计算每个唯一元素在集合中出现的频率。

问题不在于哈希码可能会发生冲突,而是如果两个本应被视为相等的元素实际上从未使用Equals 进行比较,如果它们由GetHashCode 返回的哈希码不同。


您可能也对这个问题感兴趣:

Why is it important to override GetHashCode when Equals method is overridden?


另外,我的 equals 函数中有一些逻辑,它不是直接比较所有字符串 1 对 1,所以这意味着我基本上会实现这个功能两次,以确保相等的对象获得相同的哈希码,对吗?

不一定。 Dictionary 的性能取决于散列算法(对象的散列值在用作键时也不应改变)。

如果您可以忍受一些性能损失(可能可以忽略不计),您可以使用比在 Equals 方法中使用的更简单的方法来计算哈希值(并接受更多的哈希冲突)。如果两个对象的哈希值相等,则无论如何都会调用Equals。 (事实上​​,您可以每次都返回相同的值,例如 1)。


文档中的相关部分:

Object.GetHashCode

在 Hashtable 对象中用作键的对象也必须覆盖 GetHashCode 方法,因为这些对象必须生成自己的哈希码

如果两个对象比较相等,则每个对象的 GetHashCode 方法必须返回相同的值。但是,如果两个对象比较不相等,则两个对象的 GetHashCode 方法不必返回不同的值。

【讨论】:

  • 好吧,这确实和我想的一样。但是,我不太明白在面对​​哈希冲突时如何使用哈希码作为字典的键。这是否意味着我可以为同一个键输入两个条目? (无论机会多么渺茫)
  • 没关系,我刚刚看到你关于字典使用的观点。谢谢,这真的很清楚。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-02-07
  • 1970-01-01
  • 2011-10-06
  • 1970-01-01
  • 1970-01-01
  • 2023-02-09
  • 1970-01-01
相关资源
最近更新 更多