J'essaie d'écrire des tests unitaires pour une variété d' clone()
opérations dans un grand projet et je me demande s'il existe une classe existante quelque part capable de prendre deux objets du même type, de faire une comparaison approfondie et de dire s'ils sont identiques ou pas?
99
Réponses:
Unitils a cette fonctionnalité:
la source
unitils
est défectueux précisément parce qu'il compare des variables même lorsqu'elles peuvent ne pas avoir d' impact observable . Une autre conséquence (indésirable) de la comparaison des variables est que les fermetures pures (sans état propre) ne sont pas prises en charge. De plus, les objets comparés doivent être du même type d'exécution. J'ai retroussé mes manches et créé ma propre version de l'outil de comparaison approfondie qui répond à ces préoccupations.J'adore cette question! Principalement parce qu'il est rarement répondu ou mal répondu. C'est comme si personne ne l'avait encore compris. Territoire vierge :)
Tout d'abord, ne pensez même pas à utiliser
equals
. Le contrat deequals
, tel que défini dans le javadoc, est une relation d'équivalence (réflexive, symétrique et transitive), pas une relation d'égalité. Pour cela, il faudrait aussi qu'il soit antisymétrique. La seule implémentation deequals
cela est (ou pourrait être) une vraie relation d'égalité est celle dansjava.lang.Object
. Même si vous avez utiliséequals
pour comparer tout dans le graphique, le risque de rupture du contrat est assez élevé. Comme l'a souligné Josh Bloch dans Effective Java , le contrat d'égal à égal est très facile à rompre:En plus à quoi sert une méthode booléenne de toute façon? Ce serait bien d'encapsuler réellement toutes les différences entre l'original et le clone, vous ne pensez pas? De plus, je suppose ici que vous ne voulez pas être dérangé par l'écriture / la maintenance du code de comparaison pour chaque objet du graphique, mais plutôt que vous recherchez quelque chose qui s'adapte à la source à mesure qu'elle change au fil du temps.
Soooo, ce que vous voulez vraiment, c'est une sorte d'outil de comparaison d'état. La manière dont cet outil est mis en œuvre dépend vraiment de la nature de votre modèle de domaine et de vos restrictions de performances. D'après mon expérience, il n'y a pas de solution magique générique. Et il sera lent sur un grand nombre d'itérations. Mais pour tester l'exhaustivité d'une opération de clonage, cela fera assez bien le travail. Vos deux meilleures options sont la sérialisation et la réflexion.
Quelques problèmes que vous rencontrerez:
XStream est assez rapide et combiné avec XMLUnit fera le travail en seulement quelques lignes de code. XMLUnit est agréable car il peut signaler toutes les différences, ou simplement s'arrêter à la première qu'il trouve. Et sa sortie inclut le xpath vers les différents nœuds, ce qui est bien. Par défaut, il n'autorise pas les collections non ordonnées, mais il peut être configuré pour le faire. Injection d'un gestionnaire de différence spécial (appelé un
DifferenceListener
) vous permet de spécifier la façon dont vous souhaitez traiter les différences, y compris en ignorant l'ordre. Cependant, dès que vous voulez faire quelque chose au-delà de la personnalisation la plus simple, cela devient difficile à écrire et les détails ont tendance à être liés à un objet de domaine spécifique.Ma préférence personnelle est d'utiliser la réflexion pour parcourir tous les champs déclarés et explorer chacun d'eux, en suivant les différences au fur et à mesure. Mot d'avertissement: n'utilisez pas la récursivité sauf si vous aimez les exceptions de dépassement de capacité de pile. Gardez les choses dans la portée avec une pile (utilisez un
LinkedList
ou quelque chose). J'ignore généralement les champs transitoires et statiques, et je saute les paires d'objets que j'ai déjà comparées, donc je ne me retrouve pas dans des boucles infinies si quelqu'un décide d'écrire du code auto-référentiel (Cependant, je compare toujours les wrappers primitifs quoi qu'il arrive , puisque les mêmes références d'objet sont souvent réutilisées). Vous pouvez configurer les choses à l'avance pour ignorer l'ordre des collections et pour ignorer les types ou les champs spéciaux, mais j'aime définir mes politiques de comparaison d'état sur les champs eux-mêmes via des annotations. Ceci, à mon humble avis, est exactement ce à quoi les annotations étaient destinées, pour rendre les métadonnées sur la classe disponibles au moment de l'exécution. Quelque chose comme:Je pense que c'est en fait un problème vraiment difficile, mais totalement résoluble! Et une fois que vous avez quelque chose qui fonctionne pour vous, c'est vraiment, vraiment, pratique :)
Alors bonne chance. Et si vous trouvez quelque chose de pur génie, n'oubliez pas de partager!
la source
Voir DeepEquals et DeepHashCode () dans java-util: https://github.com/jdereg/java-util
Cette classe fait exactement ce que l'auteur original demande.
la source
public
devrait s'appliquer à tous les types au lieu de s'appliquer uniquement à la collection / aux cartes.Remplacer la méthode equals ()
Vous pouvez simplement remplacer la méthode equals () de la classe en utilisant EqualsBuilder.reflectionEquals () comme expliqué ici :
la source
Juste eu à implémenter la comparaison de deux instances d'entités révisées par Hibernate Envers. J'ai commencé à écrire mes propres différences, mais j'ai ensuite trouvé le cadre suivant.
https://github.com/SQiShER/java-object-diff
Vous pouvez comparer deux objets du même type et il affichera les modifications, les ajouts et les suppressions. S'il n'y a pas de changement, les objets sont égaux (en théorie). Des annotations sont fournies pour les getters qui doivent être ignorés lors de la vérification. Le cadre a des applications beaucoup plus larges que la vérification d'égalité, c'est-à-dire que j'utilise pour générer un journal des modifications.
Ses performances sont correctes, lorsque vous comparez des entités JPA, assurez-vous de les détacher d'abord du gestionnaire d'entités.
la source
J'utilise XStream:
la source
Dans AssertJ , vous pouvez faire:
Cela ne fonctionnera probablement pas dans tous les cas, mais cela fonctionnera dans plus de cas que vous pensez.
Voici ce que dit la documentation:
la source
isEqualToComparingFieldByFieldRecursively
est désormais obsolète. UtilisezassertThat(expectedObject).usingRecursiveComparison().isEqualTo(actualObject);
plutôt :)http://www.unitils.org/tutorial-reflectionassert.html
la source
Hamcrest a le Matcher samePropertyValuesAs . Mais il repose sur la convention JavaBeans (utilise des getters et des setters). Si les objets à comparer n'ont pas de getters et de setters pour leurs attributs, cela ne fonctionnera pas.
Le bean utilisateur - avec les getters et les setters
la source
isFoo
méthode de lecture pour uneBoolean
propriété. Il y a un PR ouvert depuis 2016 pour y remédier. github.com/hamcrest/JavaHamcrest/pull/136Si vos objets implémentent Serializable, vous pouvez utiliser ceci:
la source
Votre exemple de liste liée n'est pas si difficile à gérer. Au fur et à mesure que le code parcourt les deux graphiques d'objets, il place les objets visités dans un ensemble ou une carte. Avant de passer dans une autre référence d'objet, cet ensemble est testé pour voir si l'objet a déjà été parcouru. Si oui, inutile d'aller plus loin.
Je suis d'accord avec la personne ci-dessus qui a dit utiliser une LinkedList (comme une pile mais sans méthodes synchronisées dessus, donc c'est plus rapide). Traverser le graphe d'objets à l'aide d'une pile, tout en utilisant la réflexion pour obtenir chaque champ, est la solution idéale. Écrit une fois, ce hashCode () "externe" equals () et "externe" est ce que toutes les méthodes equals () et hashCode () devraient appeler. Vous n'avez plus jamais besoin d'une méthode customer equals ().
J'ai écrit un peu de code qui traverse un graphique d'objets complet, répertorié sur Google Code. Voir json-io (http://code.google.com/p/json-io/). Il sérialise un graphe d'objets Java en JSON et en désérialise. Il gère tous les objets Java, avec ou sans constructeurs publics, sérialisables ou non sérialisables, etc. Ce même code de traversée sera la base de l'implémentation externe "equals ()" et externe "hashcode ()". Btw, le JsonReader / JsonWriter (json-io) est généralement plus rapide que le ObjectInputStream / ObjectOutputStream intégré.
Ce JsonReader / JsonWriter pourrait être utilisé à des fins de comparaison, mais cela n'aidera pas avec le hashcode. Si vous voulez un hashcode universel () et equals (), il a besoin de son propre code. Je pourrais peut-être réussir cela avec un visiteur graphique générique. Nous verrons.
Autres considérations - les champs statiques - c'est facile - ils peuvent être ignorés car toutes les instances equals () auraient la même valeur pour les champs statiques, car les champs statiques sont partagés entre toutes les instances.
Quant aux champs transitoires, ce sera une option sélectionnable. Parfois, vous voudrez peut-être que les transitoires comptent d'autres fois pas. "Parfois tu te sens comme un cinglé, parfois non."
Revenez au projet json-io (pour mes autres projets) et vous trouverez le projet externe equals () / hashcode (). Je n'ai pas encore de nom pour cela, mais ce sera évident.
la source
Apache vous donne quelque chose, convertissez les deux objets en chaîne et comparez les chaînes, mais vous devez remplacer toString ()
Remplacer toString ()
Si tous les champs sont de types primitifs:
Si vous avez des champs non primitifs et / ou une collection et / ou une carte:
la source
Je suppose que vous le savez, mais en théorie, vous êtes censé toujours remplacer .equals pour affirmer que deux objets sont vraiment égaux. Cela impliquerait qu'ils vérifient les méthodes .equals surchargées sur leurs membres.
Ce genre de chose est la raison pour laquelle .equals est défini dans Object.
Si cela était fait de manière cohérente, vous n'auriez pas de problème.
la source
Une garantie d'arrêt pour une comparaison aussi approfondie pourrait être un problème. Que doivent faire les éléments suivants? (Si vous implémentez un tel comparateur, cela ferait un bon test unitaire.)
En voici une autre:
la source
Using an answer instead of a comment to get a longer limit and better formatting.
S'il s'agit d'un commentaire, pourquoi utiliser la section réponse? C'est pourquoi je l'ai signalé. pas à cause du?
. Cette réponse est déjà signalée par quelqu'un d'autre, qui n'a pas laissé le commentaire derrière. Je viens de recevoir cela dans la file d'attente des critiques. Ça pourrait être mon mal, j'aurais dû être plus prudent.Je pense que la solution la plus simple inspirée de la solution Ray Hulha est de sérialiser l'objet, puis de comparer en profondeur le résultat brut.
La sérialisation peut être soit byte, json, xml ou simple toString, etc. ToString semble être moins cher. Lombok génère pour nous un ToSTring personnalisable facile et gratuit. Voir l'exemple ci-dessous.
la source
Depuis Java 7,
Objects.deepEquals(Object, Object)
.la source