C#: 如何对 GetHashCode 进行单元测试?

38

测试Equals方法相对来说比较直观(据我所知)。但是,如何测试GetHashCode方法呢?

7个回答

44

测试两个相等的不同对象是否具有相同的哈希码(对于各种值)。逐个改变一个方面/属性,检查不相等的对象是否给出不同的哈希码。虽然哈希码不一定需要不同,但是除非存在错误,否则选择在 恰好 给出相同哈希码的属性上选择不同值非常不幸。


7
仅因为别人的代码无法产生良好分布的哈希值并不意味着它不能成为你代码的一个好测试。 - Pete Kirkham
1
@Tony:你通常使用什么? - Svish
1
@Svish:我记不得名字了,但是重复乘法和加法——在我的关于GetHashCode的回答中找到答案,我相信你会看到很多例子 :) - Jon Skeet
10
据我所知,单元测试的目的是验证方法是否按照其文档工作。 GetHashCode的唯一要求是:“如果两个对象相等,则每个对象的GetHashCode方法必须返回相同的值。” 为不同对象返回不同的值是性能问题:GetHashCode始终返回0是严重的性能缺陷,但实际上它仍然是一个有效的哈希函数。这应该成为某种代码审查的主题,而不是单元测试。我是对的吗? - Spook
2
我想说的是,只有在GetHashCode被用户重写和实现的情况下,才需要实施一个测试来检查是否对于相等的对象返回相同的值。 - Spook
显示剩余2条评论

10

Gallio/MbUnit v3.2提供了方便的合约验证器,可以测试您对GetHashCode()IEquatable<T>的实现。具体来说,您可能会对EqualityContractHashCodeAcceptanceContract感兴趣。有关更多详细信息,请参见此处此处以及那里

public class Spot
{
  private readonly int x;
  private readonly int y;

  public Spot(int x, int y)
  {
    this.x = x;
    this.y = y;
  }

  public override int GetHashCode()
  {
    int h = -2128831035;
    h = (h * 16777619) ^ x;
    h = (h * 16777619) ^ y;
    return h;
  }
}
然后您可以像这样声明您的合约验证器:
[TestFixture]
public class SpotTest
{
  [VerifyContract]
  public readonly IContract HashCodeAcceptanceTests = new HashCodeAcceptanceContract<Spot>()
  {
    CollisionProbabilityLimit = CollisionProbability.VeryLow,
    UniformDistributionQuality = UniformDistributionQuality.Excellent,
    DistinctInstances = DataGenerators.Join(Enumerable.Range(0, 1000), Enumerable.Range(0, 1000)).Select(o => new Spot(o.First, o.Second))
  };
}

5

它与Equals()函数相似。你需要确保两个对象至少具有相同的哈希码,才能视为“相同”。这意味着如果.Equals()返回true,则哈希码也应该相同。至于正确的哈希码值是什么,这取决于你如何进行哈希。


+1 - 这绝对是需要测试的一件事。不要考虑分布,但是相同的对象必须具有相同的哈希码。 - TomTom

4

根据个人经验,除了像相同对象会给你相同的哈希码这样显而易见的事情之外,您需要创建足够大的独特对象数组,并计算其中的唯一哈希码数量。如果唯一哈希码少于总对象数的50%,则表示您的哈希函数存在问题。

        List<int> hashList = new List<int>(testObjectList.Count);
        for (int i = 0; i < testObjectList.Count; i++)
        {
            hashList.Add(testObjectList[i]);
        }

        hashList.Sort();
        int differentValues = 0;
        int curValue = hashList[0];
        for (int i = 1; i < hashList.Count; i++)
        {
            if (hashList[i] != curValue)
            {
                differentValues++;
                curValue = hashList[i];
            }
        }

        Assert.Greater(differentValues, hashList.Count/2);

1
除了检查对象相等性是否意味着哈希码的相等,以及哈希分布是否如Yann Trevin所建议的那样平坦(如果性能是一个问题),您还可以考虑如果更改对象的属性会发生什么。
假设您的对象在字典/哈希集中发生更改。您是否希望Contains(object)仍然为true?如果是,则您的GetHashCode最好不要依赖于已更改的可变属性。

0
我会预先提供一个已知/预期的哈希值,并比较 GetHashCode 的结果。

9
这使得测试非常脆弱。例如,你应该能够让GetHashCode返回它在之前版本中给出的值的相反数,并且方法仍然有效。测试你关心的内容-即相等和不相等值的哈希码比较。 - Jon Skeet

0

您可以创建具有相同值的单独实例,并检查实例的 GetHashCode 是否返回相同的值,以及对同一实例进行重复调用是否返回相同的值。

这是哈希码工作的唯一要求。当然,为了良好地工作,哈希码应该具有良好的分布,但测试需要大量的测试...


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