Java 一个单元应该如何测试 hashCode-equals 合约?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/188311/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How should one unit test the hashCode-equals contract?
提问by Paul Brinkley
In a nutshell, the hashCode contract, according to Java's object.hashCode():
简而言之,hashCode 契约,根据 Java 的 object.hashCode():
- The hash code shouldn't change unless something affecting equals() changes
- equals() implies hash codes are ==
- 除非影响 equals() 的东西发生变化,否则哈希码不应该改变
- equals() 意味着哈希码是 ==
Let's assume interest primarily in immutable data objects - their information never changes after they're constructed, so #1 is assumed to hold. That leaves #2: the problem is simply one of confirming that equals implies hash code ==.
让我们假设主要对不可变数据对象感兴趣——它们的信息在构造后永远不会改变,因此假设 #1 成立。剩下#2:问题只是确认equals隐含哈希码==的问题之一。
Obviously, we can't test every conceivable data object unless that set is trivially small. So, what is the best way to write a unit test that is likely to catch the common cases?
显然,除非该集合非常小,否则我们无法测试每个可能的数据对象。那么,编写可能捕获常见情况的单元测试的最佳方法是什么?
Since the instances of this class are immutable, there are limited ways to construct such an object; this unit test should cover all of them if possible. Off the top of my head, the entry points are the constructors, deserialization, and constructors of subclasses (which should be reducible to the constructor call problem).
由于此类的实例是不可变的,因此构造此类对象的方法有限;如果可能,这个单元测试应该涵盖所有这些。在我的脑海中,入口点是子类的构造函数、反序列化和构造函数(这应该可以简化为构造函数调用问题)。
[I'm going to try to answer my own question via research. Input from other StackOverflowers is a welcome safety mechanism to this process.]
[我将尝试通过研究来回答我自己的问题。来自其他 StackOverflowers 的输入是此过程的一种受欢迎的安全机制。]
[This could be applicable to other OO languages, so I'm adding that tag.]
[这可能适用于其他 OO 语言,所以我添加了该标签。]
回答by Eli Courtwright
My advice would be to think of why/how this might ever not hold true, and then write some unit tests which target those situations.
我的建议是考虑为什么/如何这可能永远不成立,然后编写一些针对这些情况的单元测试。
For example, let's say you had a custom Set
class. Two sets are equal if they contain the same elements, but it's possible for the underlying data structures of two equal sets to differ if those elements are stored in a different order. For example:
例如,假设您有一个自定义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() );
In this case, the order of the elements in the sets might affect their hash, depending on the hashing algorithm you've implemented. So this is the kind of test I'd write, since it tests the case where I know it would be possible for some hashing algorithm to produce different results for two objects I've defined to be equal.
在这种情况下,集合中元素的顺序可能会影响它们的散列,具体取决于您实现的散列算法。所以这是我要写的那种测试,因为它测试了我知道某些散列算法可能为我定义为相等的两个对象产生不同结果的情况。
You should use a similar standard with your own custom class, whatever that is.
无论是什么,您都应该对自己的自定义类使用类似的标准。
回答by Rob Spieldenner
This is one of the only cases where I would have multiple asserts in a test. Since you need to test the equals method you should also check the hashCode method at the same time. So on each of your equals method test cases check the hashCode contract as well.
这是我在测试中会有多个断言的唯一情况之一。由于您需要测试 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());
And of course checking for a good hashCode is another exercise. Honestly I wouldn't bother to do the double check to make sure the hashCode wasn't changing in the same run, that sort of problem is better handled by catching it in a code review and helping the developer understand why that's not a good way to write hashCode methods.
当然,检查一个好的 hashCode 是另一个练习。老实说,我不会费心进行双重检查以确保 hashCode 在同一次运行中没有改变,通过在代码中捕获它并帮助开发人员理解为什么这不是一个好方法,可以更好地处理这种问题编写 hashCode 方法。
回答by Benno Richters
I would recommend the EqualsTesterfrom GSBase. It does basically what you want. I have two (minor) problems with it though:
我会建议EqualsTester从GSBase。它基本上可以满足您的需求。我有两个(次要)问题:
- The constructor does all the work, which I don't consider to be good practice.
- It fails when an instance of class A equals to an instance of a subclass of class A. This is not necessarily a violation of the equals contract.
- 构造函数完成所有工作,我认为这不是一个好习惯。
- 当类 A 的实例等于类 A 的子类的实例时,它会失败。这不一定违反 equals 约定。
回答by Paul Brinkley
[At the time of this writing, three other answers were posted.]
[在撰写本文时,发布了其他三个答案。]
To reiterate, the aim of my question is to find standard cases of tests to confirm that hashCode
and equals
are agreeing with each other. My approach to this question is to imagine the common paths taken by programmers when writing the classes in question, namely, immutable data. For example:
重申一下,我的问题的目的是找到标准的测试案例来确认这一点hashCode
并且equals
彼此一致。我解决这个问题的方法是想象程序员在编写有问题的类时所采用的常见路径,即不可变数据。例如:
- Wrote
equals()
without writinghashCode()
.This often means equality was defined to mean equality of the fields of two instances. - Wrote
hashCode()
without writingequals()
.This may mean the programmer was seeking a more efficient hashing algorithm.
equals()
不写就写hashCode()
。这通常意味着相等被定义为两个实例的字段相等。hashCode()
不写就写equals()
。这可能意味着程序员正在寻找更有效的散列算法。
In the case of #2, the problem seems nonexistent to me. No additional instances have been made equals()
, so no additional instances are required to have equal hash codes. At worst, the hash algorithm may yield poorer performance for hash maps, which is outside the scope of this question.
在#2 的情况下,这个问题对我来说似乎不存在。没有创建额外的实例equals()
,因此不需要额外的实例具有相同的哈希码。在最坏的情况下,哈希算法可能会为哈希映射产生较差的性能,这超出了本问题的范围。
In the case of #1, the standard unit test entails creating two instances of the same object with the same data passed to the constructor, and verifying equal hash codes. What about false positives? It's possible to pick constructor parameters that just happen to yield equal hash codes on a nonetheless unsound algorithm. A unit test that tends to avoid such parameters would fulfill the spirit of this question. The shortcut here is to inspect the source code for equals()
, think hard, and write a test based on that, but while this may be necessary in some cases, there may also be common tests that catch common problems - and such tests also fulfill the spirit of this question.
在#1 的情况下,标准单元测试需要使用传递给构造函数的相同数据创建同一对象的两个实例,并验证相等的哈希码。假阳性怎么办?有可能选择刚好在不健全算法上产生相等哈希码的构造函数参数。倾向于避免此类参数的单元测试将满足这个问题的精神。这里的捷径是检查 的源代码equals()
,认真思考,然后根据它编写测试,虽然这在某些情况下可能是必要的,但也可能有常见的测试来捕捉常见的问题——而这样的测试也符合精神这个问题。
For example, if the class to be tested (call it Data) has a constructor that takes a String, and instances constructed from Strings that are equals()
yielded instances that were equals()
, then a good test would probably test:
例如,如果要测试的类(称为 Data)有一个接受 String 的构造函数,并且从 Strings 构造的equals()
实例是产生的实例equals()
,那么一个好的测试可能会测试:
new Data("foo")
- another
new Data("foo")
new Data("foo")
- 其他
new Data("foo")
We could even check the hash code for new Data(new String("foo"))
, to force the String to not be interned, although that's more likely to yield a correct hash code than Data.equals()
is to yield a correct result, in my opinion.
我们甚至可以检查 的哈希码new Data(new String("foo"))
,以强制 String 不被实习,尽管Data.equals()
在我看来,这更有可能产生正确的哈希码而不是产生正确的结果。
Eli Courtwright's answer is an example of thinking hard of a way to break the hash algorithm based on knowledge of the equals
specification. The example of a special collection is a good one, as user-made Collection
s do turn up at times, and are quite prone to muckups in the hash algorithm.
Eli Courtwright 的回答是一个基于equals
规范知识努力思考如何破解哈希算法的例子。特殊集合的例子是一个很好的例子,因为用户创建的Collection
s 有时会出现,并且很容易在散列算法中出错。
回答by Paul Brinkley
It's worth using the junit addons for this. Check out the class EqualsHashCodeTestCase http://junit-addons.sourceforge.net/you can extend this and implement createInstance and createNotEqualInstance, this will check the equals and hashCode methods are correct.
为此使用junit插件是值得的。查看类 EqualsHashCodeTestCase http://junit-addons.sourceforge.net/你可以扩展它并实现 createInstance 和 createNotEqualInstance,这将检查 equals 和 hashCode 方法是否正确。
回答by Benno Richters
EqualsVerifieris a relatively new open source project and it does a very good job at testing the equals contract. It doesn't have the issuesthe EqualsTester from GSBase has. I would definitely recommend it.
EqualsVerifier是一个相对较新的开源项目,它在测试 equals 合约方面做得非常好。它没有来自 GSBase 的 EqualsTester的问题。我肯定会推荐它。
回答by Mangoose
You can also use something similar to http://code.google.com/p/guava-libraries/source/browse/guava-testlib/src/com/google/common/testing/EqualsTester.javato test equals and hashCode.
您还可以使用类似于http://code.google.com/p/guava-libraries/source/browse/guava-testlib/src/com/google/common/testing/EqualsTester.java 的内容来测试 equals 和 hashCode。
回答by Raedwald
If I have a class Thing
, as most others do I write a class ThingTest
, which holds all the unit tests for that class. Each ThingTest
has a method
如果我有一个类Thing
,就像大多数其他人一样,我会编写一个类ThingTest
,其中包含该类的所有单元测试。每个ThingTest
都有一个方法
public static void checkInvariants(final Thing thing) {
...
}
and if the Thing
class overrides hashCode and equals it has a method
如果Thing
该类覆盖了 hashCode 并等于它有一个方法
public static void checkInvariants(final Thing thing1, Thing thing2) {
ObjectTest.checkInvariants(thing1, thing2);
... invariants that are specific to Thing
}
That method is responsible for checking allinvariants that are designed to hold between any pair of Thing
objects. The ObjectTest
method it delegates to is responsible for checking all invariants that must hold between any pair of objects. As equals
and hashCode
are methods of all objects, that method checks that hashCode
and equals
are consistent.
该方法负责检查设计为在任何一对对象之间保持的所有不变量Thing
。ObjectTest
它委托给的方法负责检查任何一对对象之间必须保持的所有不变量。由于equals
和hashCode
都是对象的方法,该方法将检查hashCode
和equals
是一致的。
I then have some test methods that create pairs of Thing
objects, and pass them to the pairwise checkInvariants
method. I use equivalence partitioning to decide what pairs are worth testing. I usually create each pair to be different in only one attribute, plus a test that tests two equivalent objects.
然后我有一些创建Thing
对象对的测试方法,并将它们传递给成对checkInvariants
方法。我使用等价分区来决定哪些对值得测试。我通常创建每对只在一个属性上不同,加上一个测试两个等效对象的测试。
I also sometimes have a 3 argument checkInvariants
method, although I finds that is less useful in findinf defects, so I do not do this often
我有时也有一个 3 参数checkInvariants
方法,虽然我发现它在 findinf 缺陷中不太有用,所以我不经常这样做