为 HashCode 方法创建单元测试

Create an unit test for an HashCode method

我有以下 HashCode 助手:

public struct HashCode {

  private readonly Int32 Value;

  private HashCode(Int32 value) {
    Value = value;
  }

  public static implicit operator Int32(HashCode hashCode) {
    return hashCode.Value;
  }

  public static HashCode Of<T>(T item) {
    return new HashCode(GetHashCode(item));
  }

  public HashCode And<T>(T item) {
    return new HashCode(CombineHashCodes(Value, GetHashCode(item)));
  }

  public HashCode AndEach<T>(IEnumerable<T> items) {      
    Int32 hashCode = items.Select(x => GetHashCode(x)).Aggregate((x, y) => CombineHashCodes(x, y));
    return new HashCode(CombineHashCodes(Value, hashCode));
  }

  private static Int32 CombineHashCodes(Int32 x, Int32 y) {

    // Taken from:
    https://github.com/dotnet/coreclr/blob/775003a4c72f0acc37eab84628fcef541533ba4e/src/mscorlib/src/System/Tuple.cs#L56

    unchecked {        
      return ((x << 5) + x) ^ y;
    }
  }

  private static Int32 GetHashCode<T>(T item) {
    return item == null ? 0 : item.GetHashCode();
  }

}

我是这样使用的:

HashCode.Of(value1).And(value2).AndEach(collection);

我应该如何创建一个单元测试来测试这个 HashCode?

创建一个或两个 类 硬编码 HashCode 然后根据您想要的粒度单元测试创​​建一个 test/tests 使用您的助手计算哈希码:

var result = HashCode
  .Of(new HardcodedHashCode(5))
  .And(new HardcodedHashCode(1));

Assert.Equals(result, manually_computed_value);

对于每次使用 HashCode 助手,您都必须手动计算预期的哈希码。我建议对 OfAndAndEach 进行一项测试,再加上使用所有这些的单一测试。

编辑更多代码:

public class HardcodedHashCode {
  private readonly int _hashCode;

    public HardcodedHashCode(int hashCode) { _hashCode = hashCode; }

    public override int GetHashCode() => _hashCode; 
}

// example test
public void and_combines_hashcodes_using_xyz_method() {
   var h1 = new HardcodedHashCode(1);
   var h5 = new HardcodedHashCode(5);

   int combinedHashcode = HashCode.of(h1).And(h5);

   // sorry but can't force myself to compute manually in the evening
   Assert.Equals(_manually_compute_value_here_, combinedHashcode);
}

我应该警告您,您尝试使用 GetHashCode 方法的方法可能有点不正确。您将 .NET 散列与您自己的散列混合在一起。而且我想您的散列与 .NET 散列具有不同的目标。

首先我要提一下,每个 .NET 对象都有自己的哈希码,并且在创建对象后不应更改。

object[] data = new object[] { 1, new Random(), 5 }; int hash1 = HashCode.AndEach(data); data[2] = "123"; int hash2 = HashCode.AndEach(data);

我假设 hash1 != hash2。对于 .NET hashing it is fail.

这就是为什么您的 HashCode.GetHashCode 实施可以结合任何延迟执行(例如 Linq)产生非常有趣的结果。

此外还有 Microsoft documentation,它表示:

A hash code is intended for efficient insertion and lookup in collections that are based on a hash table. A hash code is not a permanent value.

...

  • Do not serialize hash code values or store them in databases.

例如 Object.GetHashCode 在 .NET CLR 2.0 和 .NET CLR 4.5 中的实现不同。这就是如果您在测试中添加任何使用 Object.GetHashCode 方法的对象,您的单元测试结果在 .NET 2.0 和 .NET 4.5 中可能不同的原因。

If you override the GetHashCode method, you should also override Equals, and vice versa.

因此 csharpfolk 提供的代码对于 class HardcodedHashCode 并不完全正确。如果两个对象相等,则它们的哈希码必须相等(来自哈希定义)。 HardcodedHashCode 应该覆盖 Equals 方法,否则有人可以创建单元测试,这对于 .NET 散列来说是错误的。

所以我建议从您的代码中消除对 Object.GetHashCode 的任何调用。或者确保您的代码不违反 Microsoft 的准则。