繁体   English   中英

一个单元应该如何测试hashCode-equals合约?

[英]How should one unit test the hashCode-equals contract?

简而言之,hashCode契约,根据Java的object.hashCode():

  1. 除非影响equals()的内容发生变化,否则哈希码不应更改
  2. equals()表示哈希码是==

让我们主要关注不可变数据对象 - 它们的信息在构造之后永远不会改变,因此假定#1成立。 留下#2:问题只是确认等于隐含代码==。

显然,我们无法测试每个可想到的数据对象,除非该集合很小。 那么,编写可能会遇到常见情况的单元测试的最佳方法是什么?

由于此类的实例是不可变的,因此构造此类对象的方法有限; 如果可能的话,这个单元测试应该涵盖所有这些。 在我的脑海中,入口点是构造函数,反序列化和子类的构造函数(应该可以简化为构造函数调用问题)。

[我打算通过研究来回答我自己的问题。 来自其他StackOverflowers的输入是这个过程的一个受欢迎的安全机制。]

[这可能适用于其他OO语言,所以我添加了该标签。]

EqualsVerifier是一个相对较新的开源项目,它在测试equals合同方面做得非常好。 它没有来自GSBase的EqualsTester存在的问题 我肯定会推荐它。

我的建议是考虑为什么/如何不成立,然后写一些针对这些情况的单元测试。

例如,假设您有一个自定义的Set类。 如果两个集合包含相同的元素,则它们是相等的,但如果这些元素以不同的顺序存储,则两个相等集合的基础数据结构可能不同。 例如:

MySet s1 = new MySet( new String[]{"Hello", "World"} );
MySet s2 = new MySet( new String[]{"World", "Hello"} );
assertEquals(s1, s2);
assertTrue( s1.hashCode()==s2.hashCode() );

在这种情况下,集合中元素的顺序可能会影响它们的散列,具体取决于您实现的散列算法。 所以这就是我要写的那种测试,因为它测试的情况我知道一些散列算法可能会为我定义的两个对象产生不同的结果。

您应该使用与您自己的自定义类相似的标准,无论是什么。

值得使用junit插件。 查看类EqualsHashCodeTestCase http://junit-addons.sourceforge.net/你可以扩展这个并实现createInstance和createNotEqualInstance,这将检查equals和hashCode方法是否正确。

我会建议EqualsTester从GSBase。 它基本上是你想要的。 我有两个(小)问题但是:

  • 构造函数完成所有工作,我认为这不是一个好习惯。
  • 当类A的实例等于类A的子类的实例时,它会失败。这不一定违反equals合同。

[在撰写本文时,发布了其他三个答案。]

重申一下,我的问题的目的是找到测试的标准情况,以确认hashCodeequals彼此一致。 我对这个问题的处理方法是想象程序员在编写有问题的类时所采用的通用路径,即不可变数据。 例如:

  1. 写了equals()而没有编写hashCode() 这通常意味着将等式定义为表示两个实例的字段相等。
  2. 写了hashCode()而没有写equals() 这可能意味着程序员正在寻求更有效的散列算法。

在#2的情况下,我似乎不存在这个问题。 没有其他实例使用equals() ,因此不需要其他实例来获得相同的哈希码。 在最坏的情况下,哈希算法可能会产生较差的哈希映射性能,这超出了本问题的范围。

在#1的情况下,标准单元测试需要创建同一对象的两个实例,并将相同的数据传递给构造函数,并验证相同的哈希码。 假阳性怎么样? 有可能选择恰好在一个不合理的算法上产生相同哈希码的构造函数参数。 倾向于避免这些参数的单元测试将满足这个问题的精神。 这里的捷径是检查equals()的源代码,仔细思考,并根据它编写测试,但在某些情况下这可能是必要的,也可能有常见的测试可以捕获常见的问题 - 而且这样的测试也是实现这个问题的精神。

例如,如果要测试的类(称之为数据)有一个构造函数,需要一个String,并从该字符串是构建实例equals()得到那名实例equals() ,然后一个很好的测试将可能测试:

  • new Data("foo")
  • 另一个new Data("foo")

我们甚至可以检查new Data(new String("foo"))的哈希代码new Data(new String("foo")) ,以强制String不被实现,尽管这更可能产生正确的哈希代码而不是Data.equals()产生正确的结果,在我看来。

伊莱Courtwright的回答是一种思维方式,打破基础上的知识散列算法硬的例子equals规范。 特殊集合的例子很好,因为用户自己的Collection有时会出现,并且很容易在哈希算法中出现问题。

这是我在测试中有多个断言的唯一情况之一。 由于您需要测试equals方法,因此您还应该同时检查hashCode方法。 因此,在每个equals方法测试用例中也检查hashCode合约。

A one = new A(...);
A two = new A(...);
assertEquals("These should be equal", one, two);
int oneCode = one.hashCode();
assertEquals("HashCodes should be equal", oneCode, two.hashCode());
assertEquals("HashCode should not change", oneCode, one.hashCode());

当然,检查一个好的hashCode是另一个练习。 老实说,我不打算进行双重检查以确保hashCode在同一次运行中没有改变,通过在代码审查中捕获它并帮助开发人员理解为什么这不是一个好方法可以更好地处理这种问题编写hashCode方法。

如果我有一个类Thing ,就像大多数其他人一样,我会写一个类ThingTest ,它包含该类的所有单元测试。 每个ThingTest都有一个方法

 public static void checkInvariants(final Thing thing) {
    ...
 }

如果Thing类重写hashCode并且等于它有一个方法

 public static void checkInvariants(final Thing thing1, Thing thing2) {
    ObjectTest.checkInvariants(thing1, thing2);
    ... invariants that are specific to Thing
 }

该方法负责检查旨在保存在任何Thing对象之间的所有不变量。 它委托给它的ObjectTest方法负责检查必须在任何一对对象之间保存的所有不变量。 由于equalshashCode是所有对象的方法,因此该方法检查hashCodeequals是否一致。

然后我有一些测试方法创建Thing对象对,并将它们传递给成对checkInvariants方法。 我使用等价分区来决定哪些对值得测试。 我通常只在一个属性中创建每个对,以及测试两个等效对象的测试。

我有时也有一个3参数checkInvariants方法,虽然我发现在findinf缺陷中没那么有用,所以我不经常这样做

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM