如何为hashCode()编写单元测试?

48

如何在单元测试中测试hashCode()函数?

public int hashCode(){
    int result = 17 + hashDouble(re);
    result = 31 * result + hashDouble(im);
    return result;
}
6个回答

99
每当我重写equals和hashCode方法时,我都会编写遵循Joshua Bloch在《Effective Java》第3章中的建议的单元测试。我确保equals和hashCode是自反的、对称的和传递的。我还确保“不相等”的处理对所有数据成员都能正常工作。当我检查对equals方法的调用时,我也会确保hashCode的行为符合预期,就像这样:
@Test
public void testEquals_Symmetric() {
    Person x = new Person("Foo Bar");  // equals and hashCode check name field value
    Person y = new Person("Foo Bar");
    Assert.assertTrue(x.equals(y) && y.equals(x));
    Assert.assertTrue(x.hashCode() == y.hashCode());
}

16
除此之外,您可以补充说明合理的做法是测试对非关键字段的修改是否会导致生成修改后的hashCode。同时,对关键字段的修改确实会导致修改后的hashCodes。 - Ben Hardy
1
我认为我们不应该测试另一种方法,因为这个测试只是针对哈希码的,所以只有哈希码应该被比较,其余的代码应该被放入equals测试中并分别进行测试。 - AZ_
六年过去了,我仍然不同意你的看法。我看不出有任何区别。它们需要一起被覆盖;它们可以一起被测试。如果你需要单独的测试,那就自便吧。 - duffymo
@duffymo,为什么你用 x 和 y 来测试 .equals?你应该测试 x.hashCode() == y.hashCode() && y.hashCode() == x.hashcode() 的对称性质吧? - ennth
不是的。我已经知道==是对称的。你意识到这个问题和答案都快十年了吗? - duffymo

6
当你写一个数学函数(如哈希码)时,你需要在测试中尝试一些例子,直到你确信该函数按预期工作。需要测试的示例数量取决于函数本身。
对于哈希码函数,我认为你至少需要测试两个不同的对象,这些对象被认为是相等的,并且它们具有相同的哈希码。例如:
assertNotSame(obj1, obj2); // don't cheat
assertEquals(obj1.hashcode(), obj2.hashcode());

进一步地,您应该测试两个不同的值是否具有不同的哈希码,以避免实现hashcode()return 1;这样的代码。

1
请注意,Java不要求不相等的对象具有不同的结果。因此,在某些情况下,两个不相等的对象的哈希码可能相同,而不违反Java准则。 https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#hashCode-- - dStulle

4

为了使HashSet/HashMap等实例中具有相同字段的对象被视为相同,重写了hashCode方法。因此,Junit测试应该断言两个具有相同值的不同实例返回相同的hashCode。


1
一个测试是有用的,但测试数百万个值会更有用且仍然只需不到一秒钟的时间。 - Peter Lawrey

3

创建(数百万)可再现的随机对象,并将所有hashCode添加到Set中,检查您获得几乎与生成的id数量相同的独特值。为了使它们成为可再现的随机数,请使用固定的随机种子。

此外,检查您是否可以将这些项目添加到HashSet中并再次找到它们。(使用相同值的不同对象)

确保您的equals()与您的hashCode()行为匹配。我还会检查您的字段是否都是最终的。


26
我之所以对此进行了负面评价,是因为在单元测试中添加随机性是一件不好的事情 - 因为你首先想知道的是为什么测试失败 - 如果输入是随机的,这将非常困难。此外,您可能需要通过哈希码方法运行数十亿个对象才能获得信心,这可能意味着您的单元测试需要很长时间才能运行,这也是一件坏事。 - PaulJWilliams
1
没有给出反对的理由。提供解释会很有帮助。 - Peter Lawrey
2
@Visage,使用非随机数据如何告诉您测试失败的原因?您只需要可重复性来帮助诊断失败的测试。您无法通过任何现实的测试驱动开发达到所声称的证明水平。但是,当测试失败时,您可以说您有一个问题。 - Peter Lawrey
1
@Visage,十亿并不算太多,但百万数量级可以找到大部分的错误。仅一个测试就能惊人地发现一个bug。 - Peter Lawrey
2
我不认为这是一个糟糕的答案。如果随机性被用来建立统计档案,那么为什么会有问题呢?而且运行所需的时间长度不应该成为是否编写测试的决定因素。你可以将测试分为快速测试和长时间运行测试两种类型,前者每次都需要运行,后者则由你自行决定,除非进行了更改,否则无需运行。在我看来,彼得的回答不应该被投票否定。 - duffymo
显示剩余5条评论

1

我认为没有必要对哈希码方法进行单元测试,特别是如果它是由你的IDE或HashCodeBuilder(apache commons)生成的。


1
阅读代码可能是确保其合理的最佳方法。即通过试错很难找到病态案例。 - Peter Lawrey

1
除了 @duffymo 对哈希码进行反射、对称和传递性测试之外,另一种测试方式是通过“Map”,这就是哈希码实际派上用场的地方。
 @Test
public void testHashcode() {
    Person p1 = new Person("Foo Bar"); 
    Person p2 = new Person("Foo Bar");
    Map<Person, String> map = new HashMap<>();
    map.put(p1, "dummy");
    Assert.assertEquals("dummy", map.get(p2));
}

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