Estoy tratando de escribir pruebas unitarias para una variedad de clone()
operaciones dentro de un proyecto grande y me pregunto si existe una clase en algún lugar que sea capaz de tomar dos objetos del mismo tipo, hacer una comparación profunda y decir si ¿Eres idéntico o no?
java
comparison
equals
Uri
fuente
fuente
Respuestas:
Unitils tiene esta funcionalidad:
fuente
unitils
tiene fallas precisamente porque compara variables incluso cuando pueden no tener un impacto observable . Otra consecuencia (indeseable) de comparar variables es que los cierres puros (sin estado propio) no son compatibles. Además, requiere que los objetos comparados sean del mismo tipo de tiempo de ejecución. Me arremangué y creé mi propia versión de la herramienta de comparación profunda que aborda estas preocupaciones.¡Amo esta pregunta! Principalmente porque casi nunca se responde o se responde mal. Es como si nadie lo hubiera descubierto todavía. Territorio virgen :)
En primer lugar, ni siquiera pienses en usarlo
equals
. El contrato deequals
, como se define en el javadoc, es una relación de equivalencia (reflexiva, simétrica y transitiva), no una relación de igualdad. Para eso, también tendría que ser antisimétrico. La única implementación deequals
eso es (o podría ser) una verdadera relación de igualdad es la que se encuentra enjava.lang.Object
. Incluso si solíaequals
comparar todo en el gráfico, el riesgo de romper el contrato es bastante alto. Como señaló Josh Bloch en Effective Java , el contrato de iguales es muy fácil de romper:Además, ¿de qué te sirve realmente un método booleano? Sería bueno encapsular todas las diferencias entre el original y el clon, ¿no crees? Además, asumiré aquí que no quiere preocuparse por escribir / mantener el código de comparación para cada objeto en el gráfico, sino que está buscando algo que se adapte a la fuente a medida que cambia con el tiempo.
Entonces, lo que realmente quieres es algún tipo de herramienta de comparación de estados. La forma en que se implementa esa herramienta depende realmente de la naturaleza de su modelo de dominio y sus restricciones de rendimiento. En mi experiencia, no existe una fórmula mágica genérica. Y será lento en una gran cantidad de iteraciones. Pero para probar la integridad de una operación de clonación, funcionará bastante bien. Sus dos mejores opciones son la serialización y la reflexión.
Algunos problemas que encontrará:
XStream es bastante rápido y combinado con XMLUnit hará el trabajo en solo unas pocas líneas de código. XMLUnit es bueno porque puede informar todas las diferencias, o simplemente detenerse en la primera que encuentre. Y su salida incluye el xpath a los diferentes nodos, lo cual es bueno. De forma predeterminada, no permite colecciones desordenadas, pero se puede configurar para que lo haga. La inyección de un controlador de diferencias especial (denominado a
DifferenceListener
) le permite especificar la forma en que desea tratar las diferencias, incluido el ignorar el orden. Sin embargo, tan pronto como quiera hacer algo más allá de la personalización más simple, se vuelve difícil escribir y los detalles tienden a estar ligados a un objeto de dominio específico.Mi preferencia personal es usar la reflexión para recorrer todos los campos declarados y profundizar en cada uno, rastreando las diferencias a medida que avanzo. Advertencia: no utilice la recursividad a menos que le gusten las excepciones de desbordamiento de pila. Mantenga las cosas dentro del alcance con una pila (use un
LinkedList
o algo). Por lo general, ignoro los campos estáticos y transitorios, y omito pares de objetos que ya he comparado, por lo que no termino en bucles infinitos si alguien decide escribir código autorreferencial (sin embargo, siempre comparo envoltorios primitivos sin importar qué , ya que las mismas referencias de objeto a menudo se reutilizan). Puede configurar las cosas por adelantado para ignorar el orden de la colección y para ignorar tipos o campos especiales, pero me gusta definir mis políticas de comparación de estado en los campos mismos a través de anotaciones. Esto, en mi humilde opinión, es exactamente para lo que estaban destinadas las anotaciones, para hacer que los metadatos sobre la clase estén disponibles en tiempo de ejecución. Algo como:Creo que este es un problema realmente difícil, ¡pero totalmente solucionable! Y una vez que tenga algo que funcione para usted, es realmente muy útil :)
Buena suerte. Y si se te ocurre algo que es pura genialidad, ¡no olvides compartirlo!
fuente
Consulte DeepEquals y DeepHashCode () dentro de java-util: https://github.com/jdereg/java-util
Esta clase hace exactamente lo que solicita el autor original.
fuente
public
debería aplicarse a todos los tipos en lugar de solo ser aplicable a colecciones / mapas.Anular el método equals ()
Simplemente puede anular el método equals () de la clase utilizando EqualsBuilder.reflectionEquals () como se explica aquí :
fuente
Solo tuve que implementar la comparación de dos instancias de entidad revisadas por Hibernate Envers. Comencé a escribir mi propia diferencia, pero luego encontré el siguiente marco.
https://github.com/SQiShER/java-object-diff
Puede comparar dos objetos del mismo tipo y mostrará cambios, adiciones y eliminaciones. Si no hay cambios, entonces los objetos son iguales (en teoría). Se proporcionan anotaciones para los captadores que deben ignorarse durante la verificación. El marco de trabajo tiene aplicaciones mucho más amplias que la verificación de igualdad, es decir, estoy usando para generar un registro de cambios.
Su rendimiento es correcto, al comparar entidades JPA, asegúrese de separarlas del administrador de la entidad primero.
fuente
Estoy usando XStream:
fuente
En AssertJ , puede hacer:
Probablemente no funcionará en todos los casos, sin embargo, funcionará en más casos de los que cree.
Esto es lo que dice la documentación:
fuente
isEqualToComparingFieldByFieldRecursively
ahora está en desuso. Úselo en suassertThat(expectedObject).usingRecursiveComparison().isEqualTo(actualObject);
lugar :)http://www.unitils.org/tutorial-reflectionassert.html
fuente
Hamcrest tiene el Matcher samePropertyValuesAs . Pero se basa en la Convención de JavaBeans (usa getters y setters). Si los objetos que se van a comparar no tienen getters y setters para sus atributos, esto no funcionará.
El bean de usuario - con getters y setters
fuente
isFoo
método de lectura para unaBoolean
propiedad. Hay un PR que ha estado abierto desde 2016 para solucionarlo. github.com/hamcrest/JavaHamcrest/pull/136Si sus objetos implementan Serializable, puede usar esto:
fuente
Su ejemplo de Linked List no es tan difícil de manejar. A medida que el código atraviesa los gráficos de dos objetos, coloca los objetos visitados en un Conjunto o Mapa. Antes de atravesar otra referencia de objeto, este conjunto se prueba para ver si el objeto ya ha sido atravesado. Si es así, no es necesario ir más lejos.
Estoy de acuerdo con la persona de arriba que dijo usar una LinkedList (como una Pila pero sin métodos sincronizados, por lo que es más rápido). Atravesar el gráfico de objetos usando una pila, mientras se usa la reflexión para obtener cada campo, es la solución ideal. Escrito una vez, este hashCode () "externo" equals () y "externo" es lo que todos los métodos equals () y hashCode () deben llamar. Nunca más necesitará un método de cliente igual ().
Escribí un fragmento de código que atraviesa un gráfico de objetos completo, incluido en Google Code. Consulte json-io (http://code.google.com/p/json-io/). Serializa un gráfico de objetos Java en JSON y lo deserializa. Maneja todos los objetos Java, con o sin constructores públicos, serializables o no serializables, etc. Este mismo código transversal será la base para la implementación externa "equals ()" y externa "hashcode ()". Por cierto, JsonReader / JsonWriter (json-io) suele ser más rápido que el ObjectInputStream / ObjectOutputStream integrado.
Este JsonReader / JsonWriter podría usarse para comparar, pero no ayudará con el código hash. Si desea un código hash universal () y equals (), necesita su propio código. Es posible que pueda lograr esto con un visitante de gráfico genérico. Ya veremos.
Otras consideraciones, campos estáticos, eso es fácil, se pueden omitir porque todas las instancias equals () tendrían el mismo valor para los campos estáticos, ya que los campos estáticos se comparten entre todas las instancias.
En cuanto a los campos transitorios, esa será una opción seleccionable. A veces es posible que desee que los transitorios cuenten otras veces no. "A veces te sientes como un loco, a veces no".
Vuelva al proyecto json-io (para mis otros proyectos) y encontrará el proyecto externo equals () / hashcode (). Todavía no tengo un nombre, pero será obvio.
fuente
Apache le da algo, convierta ambos objetos en cadenas y compare cadenas, pero debe anular toString ()
Anular toString ()
Si todos los campos son tipos primitivos:
Si tiene campos no primitivos y / o colección y / o mapa:
fuente
Supongo que lo sabe, pero en teoría, se supone que siempre debe anular .equals para afirmar que dos objetos son realmente iguales. Esto implicaría que verifican los métodos .equals reemplazados en sus miembros.
Este tipo de cosas es la razón por la que .equals se define en Object.
Si esto se hiciera de manera consistente, no tendría ningún problema.
fuente
Una garantía vacilante para una comparación tan profunda podría ser un problema. ¿Qué debe hacer lo siguiente? (Si implementa un comparador de este tipo, sería una buena prueba unitaria).
Aquí está otro:
fuente
Using an answer instead of a comment to get a longer limit and better formatting.
Si esto es un comentario, entonces, ¿por qué usar la sección de respuestas? Por eso lo marqué. no por el?
. Esta respuesta ya está marcada por otra persona, que no dejó el comentario atrás. Acabo de recibir esto en la cola de revisión. Podría ser mi culpa, debería haber tenido más cuidado.Creo que la solución más fácil inspirada por la solución de Ray Hulha es serializar el objeto y luego comparar en profundidad el resultado sin procesar.
La serialización puede ser byte, json, xml o simple toString, etc. ToString parece ser más económico. Lombok genera ToSTring personalizable y gratuito para nosotros. Vea el ejemplo a continuación.
fuente
Desde Java 7,
Objects.deepEquals(Object, Object)
.fuente