4
public class Foo
{
    public int X { get; set; }
    public int Y { get; set; }
    public int Z { get; set; }

    public override int GetHashCode()
    {
        var hash = 17;
        hash *= 23 + x.GetHashCode();
        hash *= 23 + y.GetHashCode();
        hash *= 23 + z.GetHashCode();
    }
}

当您对 GetHashCode 进行单元测试时,我在计算原始组件和重复函数或使用预定值之间纠结:

[TestMethod]
public void Test1
{
    var x = 1; y = 2; z = 3;
    var foo = new Foo() { X = x, Y = y, Z = z };

    var hash = 17;
    hash *= 23 + x.GetHashCode();
    hash *= 23 + y.GetHashCode();
    hash *= 23 + z.GetHashCode();

    var expected = hash;
    var actual = foo.GetHashCode();

    Assert.AreEqual(expected, actual);
}

[TestMethod]
public void Test2
{
    var x = 1; y = 2; z = 3;
    var foo = new Foo() { X = x, Y = y, Z = z };

    var expected = ? //Some predetermined value (calculated by hand?)
    var actual = foo.GetHashCode();

    Assert.AreEqual(expected, actual);
}

还是有其他方法?

4

2 回答 2

5

单元测试用于测试逻辑。计算本身是否符合GetHashCode逻辑?并非如此,尽管意见可能会有所不同。

这里的相关逻辑是,两个相等的对象具有相同的哈希码,即Equals兼容HashCode。或者,引用文档

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

  • 只要确定对象的 Equals 方法的返回值的对象状态没有修改,对象的 GetHashCode 方法就必须始终返回相同的哈希码。请注意,这仅适用于应用程序的当前执行,并且如果再次运行应用程序,则可以返回不同的哈希码。

所以我会编写单元测试来保证满足这些条件,而不是内部实现GetHashCode与预定过程相匹配。

您的两个示例测试都非常脆弱,完全有效的GetHashCode实现会使它们失败。回想一下,单元测试的主要目的之一是允许重构而不用担心。GetHashCode但是,没有人能在你的单元测试不中断的情况下进行重构。

于 2011-07-13T18:54:27.627 回答
1

我将结果与预先计算的值进行了比较,并附有大量评论,以解释为什么我选择了正在测试的值。

对于纯计算,我倾向于编写单元测试,以确保函数保持将使用它的代码所需/预期的条件。

示例条件:

  • 生成的哈希值是否有任何限制?最小值、最大值、奇数、偶数等
  • 它是否正确散列正数和负数?
  • 它是否在适当的条件下创造了独特的价值?
于 2011-07-13T19:02:58.960 回答