Kurz gesagt, der HashCode-Vertrag gemäß Javas object.hashCode ():
- Der Hash-Code sollte sich nur ändern, wenn sich etwas auf equals () auswirkt
- equals () impliziert, dass Hash-Codes == sind
Nehmen wir vor allem das Interesse an unveränderlichen Datenobjekten an - ihre Informationen ändern sich nach ihrer Erstellung nie mehr, daher wird angenommen, dass # 1 gilt. Damit bleibt # 2: Das Problem besteht einfach darin, zu bestätigen, dass gleich Hash-Code == impliziert.
Offensichtlich können wir nicht jedes denkbare Datenobjekt testen, es sei denn, diese Menge ist trivial klein. Was ist also der beste Weg, um einen Komponententest zu schreiben, der wahrscheinlich die häufigsten Fälle erfasst?
Da die Instanzen dieser Klasse unveränderlich sind, gibt es nur begrenzte Möglichkeiten, ein solches Objekt zu erstellen. Dieser Unit-Test sollte nach Möglichkeit alle abdecken. Die Einstiegspunkte sind die Konstruktoren, Deserialisierungen und Konstruktoren von Unterklassen (die auf das Konstruktoraufrufproblem reduziert werden sollten).
[Ich werde versuchen, meine eigene Frage durch Recherche zu beantworten. Die Eingabe von anderen StackOverflowers ist ein willkommener Sicherheitsmechanismus für diesen Prozess.]
[Dies könnte auf andere OO-Sprachen anwendbar sein, daher füge ich dieses Tag hinzu.]
quelle
Antworten:
EqualsVerifier ist ein relativ neues Open-Source-Projekt und leistet sehr gute Arbeit beim Testen des Equals-Vertrags. Es hat nicht die Probleme, die der EqualsTester von GSBase hat. Ich würde es auf jeden Fall empfehlen.
quelle
Mein Rat wäre, darüber nachzudenken, warum / wie dies jemals nicht zutreffen könnte, und dann einige Komponententests zu schreiben, die auf diese Situationen abzielen.
Angenommen, Sie hatten eine benutzerdefinierte
Set
Klasse. Zwei Mengen sind gleich, wenn sie dieselben Elemente enthalten. Es ist jedoch möglich, dass sich die zugrunde liegenden Datenstrukturen von zwei gleichen Mengen unterscheiden, wenn diese Elemente in einer anderen Reihenfolge gespeichert werden. Zum Beispiel: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 diesem Fall kann sich die Reihenfolge der Elemente in den Mengen auf ihren Hash auswirken, abhängig von dem von Ihnen implementierten Hashing-Algorithmus. Dies ist also die Art von Test, die ich schreiben würde, da er den Fall testet, in dem ich weiß, dass ein Hashing-Algorithmus für zwei Objekte, die ich als gleich definiert habe, unterschiedliche Ergebnisse erzielen kann.
Sie sollten einen ähnlichen Standard mit Ihrer eigenen benutzerdefinierten Klasse verwenden, was auch immer das ist.
quelle
Es lohnt sich, dafür die Junit-Addons zu verwenden. Schauen Sie sich die Klasse EqualsHashCodeTestCase http://junit-addons.sourceforge.net/ an. Sie können diese erweitern und createInstance und createNotEqualInstance implementieren. Dadurch wird überprüft, ob die Methoden equals und hashCode korrekt sind.
quelle
Ich würde den EqualsTester von GSBase empfehlen. Es macht im Grunde, was Sie wollen. Ich habe jedoch zwei (kleinere) Probleme damit:
quelle
[Zum Zeitpunkt dieses Schreibens wurden drei weitere Antworten veröffentlicht.]
Um es noch einmal zu wiederholen, das Ziel meiner Frage ist es, Standardfälle von Tests zu finden, um dies zu bestätigen
hashCode
undequals
miteinander übereinzustimmen. Mein Ansatz für diese Frage besteht darin, mir die gemeinsamen Wege vorzustellen, die Programmierer beim Schreiben der fraglichen Klassen eingeschlagen haben, nämlich unveränderliche Daten. Zum Beispiel:equals()
ohne zu schreibenhashCode()
. Dies bedeutet häufig, dass Gleichheit als Gleichheit der Felder zweier Instanzen definiert wurde.hashCode()
ohne zu schreibenequals()
. Dies könnte bedeuten, dass der Programmierer nach einem effizienteren Hashing-Algorithmus suchte.Im Fall von # 2 scheint mir das Problem nicht zu existieren. Es wurden keine zusätzlichen Instanzen erstellt
equals()
, daher sind keine zusätzlichen Instanzen erforderlich, um gleiche Hash-Codes zu haben. Im schlimmsten Fall kann der Hash-Algorithmus zu einer schlechteren Leistung für Hash-Maps führen, was außerhalb des Rahmens dieser Frage liegt.Im Fall von # 1 umfasst der Standard-Komponententest das Erstellen von zwei Instanzen desselben Objekts mit denselben Daten, die an den Konstruktor übergeben werden, und das Überprüfen gleicher Hash-Codes. Was ist mit Fehlalarmen? Es ist möglich, Konstruktorparameter auszuwählen, die zufällig gleiche Hash-Codes auf einem dennoch unsoliden Algorithmus ergeben. Ein Unit-Test, der dazu neigt, solche Parameter zu vermeiden, würde den Geist dieser Frage erfüllen. Die Abkürzung hier besteht darin, den Quellcode zu überprüfen, gründlich
equals()
nachzudenken und einen darauf basierenden Test zu schreiben. In einigen Fällen kann dies zwar erforderlich sein, es kann jedoch auch allgemeine Tests geben, die häufig auftretende Probleme auffangen - und solche Tests erfüllen auch den Geist dieser Frage.Wenn die zu testende Klasse (nennen Sie sie Daten) beispielsweise einen Konstruktor hat, der einen String akzeptiert, und Instanzen, die aus Strings erstellt wurden und Instanzen
equals()
ergeben, die es warenequals()
, würde ein guter Test wahrscheinlich Folgendes testen:new Data("foo")
new Data("foo")
Wir könnten sogar den Hash-Code überprüfen
new Data(new String("foo"))
, um zu erzwingen, dass der String nicht interniert wird, obwohl diesData.equals()
meiner Meinung nach eher zu einem korrekten Hash-Code als zu einem korrekten Ergebnis führt.Die Antwort von Eli Courtwright ist ein Beispiel dafür, wie man überlegt, wie man den Hash-Algorithmus auf der Grundlage der Kenntnis der
equals
Spezifikation brechen kann . Das Beispiel einer speziellen Sammlung ist gut, da benutzerdefinierteCollection
s manchmal auftauchen und im Hash-Algorithmus sehr anfällig für Fehler sind.quelle
Dies ist einer der wenigen Fälle, in denen ich in einem Test mehrere Asserts hätte. Da Sie die Methode equals testen müssen, sollten Sie gleichzeitig auch die Methode hashCode überprüfen. Überprüfen Sie daher in jedem Testfall mit gleicher Methode auch den HashCode-Vertrag.
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());
Und natürlich ist es eine weitere Übung, nach einem guten Hashcode zu suchen. Ehrlich gesagt würde ich mir nicht die Mühe machen, die doppelte Überprüfung durchzuführen, um sicherzustellen, dass sich der HashCode nicht im selben Lauf ändert. Diese Art von Problem lässt sich besser lösen, indem ich es in einer Codeüberprüfung abfange und dem Entwickler helfe, zu verstehen, warum dies kein guter Weg ist HashCode-Methoden schreiben.
quelle
Sie können auch etwas Ähnliches wie http://code.google.com/p/guava-libraries/source/browse/guava-testlib/src/com/google/common/testing/EqualsTester.java verwenden , um Equals und HashCode zu testen.
quelle
Wenn ich eine Klasse habe
Thing
, schreibe ich wie die meisten anderen eine KlasseThingTest
, die alle Komponententests für diese Klasse enthält. JederThingTest
hat eine Methodepublic static void checkInvariants(final Thing thing) { ... }
und wenn die
Thing
Klasse hashCode überschreibt und gleich ist, hat sie eine Methodepublic static void checkInvariants(final Thing thing1, Thing thing2) { ObjectTest.checkInvariants(thing1, thing2); ... invariants that are specific to Thing }
Diese Methode ist dafür verantwortlich, alle Invarianten zu überprüfen , die zwischen zwei Objektpaaren gespeichert werden sollen
Thing
. DieObjectTest
Methode, an die delegiert wird, ist für die Überprüfung aller Invarianten verantwortlich, die zwischen zwei Objektpaaren enthalten sein müssen. Daequals
undhashCode
Methoden aller Objekte sind, überprüft diese Methode dieshashCode
undequals
ist konsistent.Ich habe dann einige Testmethoden, die Paare von
Thing
Objekten erstellen und diese an die paarweisecheckInvariants
Methode übergeben. Ich verwende die Äquivalenzpartitionierung, um zu entscheiden, welche Paare es wert sind, getestet zu werden. Normalerweise erstelle ich jedes Paar so, dass es nur in einem Attribut unterschiedlich ist, plus einem Test, der zwei äquivalente Objekte testet.Ich habe auch manchmal eine 3-Argument-
checkInvariants
Methode, obwohl ich finde, dass dies bei Findinf-Fehlern weniger nützlich ist, deshalb mache ich das nicht oftquelle