Ich versuche, Unit-Tests für eine Vielzahl von clone()
Operationen in einem großen Projekt zu schreiben, und ich frage mich, ob es irgendwo eine Klasse gibt, die in der Lage ist, zwei Objekte desselben Typs zu nehmen, einen gründlichen Vergleich durchzuführen und zu sagen, ob sie vorhanden sind Sind Sie identisch oder nicht?
java
comparison
equals
Uri
quelle
quelle
Antworten:
Unitils hat diese Funktionalität:
quelle
unitils
genau deshalb fehlerhaft ist, weil es Variablen vergleicht, auch wenn sie möglicherweise keine beobachtbaren Auswirkungen haben . Eine weitere (unerwünschte) Folge des Vergleichs von Variablen ist, dass reine Verschlüsse (ohne eigenen Zustand) nicht unterstützt werden. Außerdem müssen die verglichenen Objekte vom gleichen Laufzeittyp sein. Ich krempelte die Ärmel hoch und erstellte meine eigene Version des Deep-Compare-Tools, mit dem diese Probleme behoben werden.Ich liebe diese Frage! Hauptsächlich, weil es kaum jemals schlecht oder schlecht beantwortet wird. Es ist, als hätte es noch niemand herausgefunden. Neuland :)
Denken Sie zunächst einmal nicht einmal an die Verwendung
equals
. Der Vertrag vonequals
, wie im Javadoc definiert, ist eine Äquivalenzbeziehung (reflexiv, symmetrisch und transitiv), keine Gleichheitsbeziehung. Dafür müsste es auch antisymmetrisch sein. Die einzige Implementierungequals
davon ist (oder könnte jemals sein) eine echte Gleichheitsbeziehung ist die injava.lang.Object
. Selbst wenn Sieequals
alles in der Grafik verglichen haben, ist das Risiko eines Vertragsbruchs recht hoch. Wie Josh Bloch in Effective Java betonte, ist der Gleichstellungsvertrag sehr leicht zu brechen:Abgesehen davon, was nützt Ihnen eine boolesche Methode überhaupt? Es wäre schön, tatsächlich alle Unterschiede zwischen dem Original und dem Klon zusammenzufassen, finden Sie nicht? Außerdem gehe ich hier davon aus, dass Sie sich nicht die Mühe machen möchten, Vergleichscode für jedes Objekt in der Grafik zu schreiben / zu pflegen, sondern nach etwas suchen, das mit der Quelle skaliert, wenn es sich im Laufe der Zeit ändert.
Soooo, was Sie wirklich wollen, ist eine Art Zustandsvergleichstool. Wie dieses Tool implementiert wird, hängt wirklich von der Art Ihres Domänenmodells und Ihren Leistungsbeschränkungen ab. Nach meiner Erfahrung gibt es kein generisches Wundermittel. Und es wird über eine große Anzahl von Iterationen langsam sein. Aber um die Vollständigkeit einer Klonoperation zu testen, wird es die Arbeit ziemlich gut machen. Ihre beiden besten Optionen sind Serialisierung und Reflektion.
Einige Probleme, auf die Sie stoßen werden:
XStream ist ziemlich schnell und in Kombination mit XMLUnit erledigt dies die Aufgabe in nur wenigen Codezeilen. XMLUnit ist nett, weil es alle Unterschiede melden kann oder einfach beim ersten Stopp anhält. Und seine Ausgabe enthält den xpath zu den verschiedenen Knoten, was sehr schön ist. Standardmäßig sind keine ungeordneten Sammlungen zulässig, es kann jedoch dafür konfiguriert werden. Durch Injizieren eines speziellen Differenz-Handlers (a genannt
DifferenceListener
) können Sie festlegen, wie mit Unterschieden umgegangen werden soll, einschließlich des Ignorierens der Reihenfolge. Sobald Sie jedoch etwas tun möchten, das über die einfachste Anpassung hinausgeht, wird das Schreiben schwierig und die Details sind in der Regel an ein bestimmtes Domänenobjekt gebunden.Meine persönliche Präferenz ist es, mithilfe der Reflexion alle deklarierten Felder zu durchlaufen und einen Drilldown in die einzelnen Felder durchzuführen, um die Unterschiede zu verfolgen. Warnung: Verwenden Sie keine Rekursion, es sei denn, Sie mögen Stapelüberlauf-Ausnahmen. Halten Sie die Dinge mit einem Stapel im Umfang (verwenden Sie a
LinkedList
oder so). Normalerweise ignoriere ich transiente und statische Felder und überspringe Objektpaare, die ich bereits verglichen habe, damit ich nicht in Endlosschleifen gerate, wenn sich jemand dazu entschließt, selbstreferenziellen Code zu schreiben (ich vergleiche jedoch immer primitive Wrapper, egal was passiert , da die gleichen Objektreferenzen häufig wiederverwendet werden). Sie können die Dinge im Voraus so konfigurieren, dass die Sammlungsreihenfolge ignoriert und spezielle Typen oder Felder ignoriert werden. Ich möchte jedoch meine Statusvergleichsrichtlinien für die Felder selbst über Anmerkungen definieren. Dies ist meiner Meinung nach genau das, wofür Annotationen gedacht waren, um Metadaten über die Klasse zur Laufzeit verfügbar zu machen. Etwas wie:Ich denke, das ist tatsächlich ein sehr schweres Problem, aber völlig lösbar! Und wenn Sie etwas haben, das für Sie funktioniert, ist es wirklich sehr, sehr praktisch :)
Also viel Glück. Und wenn Sie sich etwas einfallen lassen, das einfach nur genial ist, vergessen Sie nicht, es zu teilen!
quelle
Siehe DeepEquals und DeepHashCode () in java-util: https://github.com/jdereg/java-util
Diese Klasse macht genau das, was der ursprüngliche Autor verlangt.
quelle
public
für alle Typen gelten sollte, anstatt nur für Sammlungen / Karten.Override Die equals () -Methode
Sie können die equals () -Methode der Klasse einfach mit EqualsBuilder.reflectionEquals () überschreiben, wie hier erläutert :
quelle
Es musste lediglich ein Vergleich zweier von Hibernate Envers überarbeiteter Entitätsinstanzen implementiert werden. Ich fing an, meinen eigenen Unterschied zu schreiben, fand dann aber den folgenden Rahmen.
https://github.com/SQiShER/java-object-diff
Sie können zwei Objekte desselben Typs vergleichen und es werden Änderungen, Ergänzungen und Entfernungen angezeigt. Wenn es keine Änderungen gibt, sind die Objekte (theoretisch) gleich. Für Getter werden Anmerkungen bereitgestellt, die bei der Überprüfung ignoriert werden sollten. Das Rahmenwerk hat weitaus umfassendere Anwendungen als die Gleichheitsprüfung, dh ich verwende es, um ein Änderungsprotokoll zu erstellen.
Die Leistung ist in Ordnung. Wenn Sie JPA-Entitäten vergleichen, müssen Sie sie zuerst vom Entitätsmanager trennen.
quelle
Ich benutze XStream:
quelle
In AssertJ können Sie Folgendes tun:
Wahrscheinlich wird es nicht in allen Fällen funktionieren, aber es wird in mehr Fällen funktionieren, als Sie denken würden.
In der Dokumentation heißt es:
quelle
isEqualToComparingFieldByFieldRecursively
ist jetzt veraltet. Verwenden SieassertThat(expectedObject).usingRecursiveComparison().isEqualTo(actualObject);
stattdessen :)http://www.unitils.org/tutorial-reflectionassert.html
quelle
Hamcrest hat den Matcher samePropertyValuesAs . Es basiert jedoch auf der JavaBeans-Konvention (verwendet Getter und Setter). Sollten die zu vergleichenden Objekte keine Getter und Setter für ihre Attribute haben, funktioniert dies nicht.
Die User Bean - mit Getter und Setter
quelle
isFoo
für eineBoolean
Eigenschaft verwendet. Es gibt eine PR, die seit 2016 geöffnet ist, um das Problem zu beheben. github.com/hamcrest/JavaHamcrest/pull/136Wenn Ihre Objekte Serializable implementieren, können Sie Folgendes verwenden:
quelle
Ihr Beispiel für eine verknüpfte Liste ist nicht so schwer zu handhaben. Während der Code die beiden Objektdiagramme durchläuft, platziert er besuchte Objekte in einem Set oder einer Karte. Vor dem Durchlaufen einer anderen Objektreferenz wird dieser Satz getestet, um festzustellen, ob das Objekt bereits durchlaufen wurde. Wenn ja, müssen Sie nicht weiter gehen.
Ich stimme der oben genannten Person zu, die sagte, eine LinkedList zu verwenden (wie ein Stack, jedoch ohne synchronisierte Methoden, daher ist es schneller). Das Durchlaufen des Objektgraphen mit einem Stapel und die Verwendung der Reflexion zum Abrufen jedes Felds ist die ideale Lösung. Einmal geschrieben, sollten diese "externen" Methoden equals () und "external" hashCode () alle Methoden equals () und hashCode () aufrufen. Nie wieder brauchen Sie eine Kundengleichmethode ().
Ich habe ein bisschen Code geschrieben, der ein vollständiges Objektdiagramm durchläuft, das bei Google Code aufgelistet ist. Siehe json-io (http://code.google.com/p/json-io/). Es serialisiert einen Java-Objektgraphen in JSON und deserialisiert daraus. Es behandelt alle Java-Objekte mit oder ohne öffentliche Konstruktoren, serialisierbar oder nicht serialisierbar usw. Derselbe Traversal-Code ist die Grundlage für die externe Implementierung von "equals ()" und externem "hashcode ()". Übrigens ist der JsonReader / JsonWriter (json-io) normalerweise schneller als der integrierte ObjectInputStream / ObjectOutputStream.
Dieser JsonReader / JsonWriter könnte zum Vergleich verwendet werden, hilft aber nicht mit Hashcode. Wenn Sie einen universellen Hashcode () und equals () möchten, benötigen Sie einen eigenen Code. Ich kann dies möglicherweise mit einem generischen Grafikbesucher durchziehen. Wir werden sehen.
Andere Überlegungen - statische Felder - das ist einfach - sie können übersprungen werden, da alle equals () -Instanzen für statische Felder den gleichen Wert haben würden, da die statischen Felder von allen Instanzen gemeinsam genutzt werden.
Transiente Felder sind eine auswählbare Option. Manchmal möchten Sie vielleicht, dass Transienten andere Male nicht zählen. "Manchmal fühlst du dich wie eine Nuss, manchmal nicht."
Wenn Sie zum json-io-Projekt zurückkehren (für meine anderen Projekte), finden Sie das externe Projekt equals () / hashcode (). Ich habe noch keinen Namen dafür, aber es wird offensichtlich sein.
quelle
Apache gibt Ihnen etwas, konvertiert beide Objekte in Zeichenfolgen und vergleicht Zeichenfolgen, aber Sie müssen toString () überschreiben
OverSide toString ()
Wenn alle Felder primitive Typen sind:
Wenn Sie nicht primitive Felder und / oder Sammlungen und / oder Karten haben:
quelle
Ich denke, Sie wissen das, aber theoretisch sollten Sie immer .equals überschreiben, um zu behaupten, dass zwei Objekte wirklich gleich sind. Dies würde bedeuten, dass sie die überschriebenen .equals-Methoden für ihre Mitglieder überprüfen.
Aus diesem Grund wird .equals in Object definiert.
Wenn dies konsequent gemacht würde, hätten Sie kein Problem.
quelle
Eine Haltegarantie für einen so tiefen Vergleich könnte ein Problem sein. Was soll das Folgende tun? (Wenn Sie einen solchen Komparator implementieren, wäre dies ein guter Komponententest.)
Hier ist ein anderes:
quelle
Using an answer instead of a comment to get a longer limit and better formatting.
Wenn dies ein Kommentar ist, warum dann den Antwortbereich verwenden? Deshalb habe ich es markiert. nicht wegen der?
. Diese Antwort wurde bereits von einer anderen Person markiert, die den Kommentar nicht hinterlassen hat. Ich habe das gerade in der Überprüfungswarteschlange. Könnte mein schlechtes sein, ich hätte vorsichtiger sein sollen.Ich denke, die einfachste Lösung, die von der Ray Hulha- Lösung inspiriert wurde , besteht darin, das Objekt zu serialisieren und dann das Rohergebnis gründlich zu vergleichen.
Die Serialisierung kann entweder Byte, JSON, XML oder einfach toString usw. sein. ToString scheint billiger zu sein. Lombok generiert für uns einen kostenlosen, einfach anpassbaren ToSTring. Siehe Beispiel unten.
quelle
Seit Java 7 ,
Objects.deepEquals(Object, Object)
.quelle