En pocas palabras, el contrato hashCode, según el object.hashCode () de Java:
- El código hash no debería cambiar a menos que algo que afecte a equals () cambie
- equals () implica que los códigos hash son ==
Supongamos que se interesa principalmente en los objetos de datos inmutables: su información nunca cambia después de que se construyen, por lo que se supone que el n. Eso deja el número 2: el problema es simplemente confirmar que igual implica código hash ==.
Obviamente, no podemos probar todos los objetos de datos imaginables a menos que ese conjunto sea trivialmente pequeño. Entonces, ¿cuál es la mejor manera de escribir una prueba unitaria que probablemente detecte los casos comunes?
Dado que las instancias de esta clase son inmutables, hay formas limitadas de construir tal objeto; esta prueba unitaria debe cubrir todos ellos si es posible. En la parte superior de mi cabeza, los puntos de entrada son los constructores, la deserialización y los constructores de subclases (que deberían ser reducibles al problema de llamada al constructor).
[Voy a intentar responder a mi propia pregunta mediante la investigación. La entrada de otros StackOverflowers es un mecanismo de seguridad bienvenido para este proceso.]
[Esto podría aplicarse a otros idiomas de OO, así que agrego esa etiqueta].
fuente
Respuestas:
EqualsVerifier es un proyecto de código abierto relativamente nuevo y hace un muy buen trabajo probando el contrato de igualdad. No tiene los problemas que tiene EqualsTester de GSBase. Sin duda lo recomendaría.
fuente
Mi consejo sería pensar en por qué / cómo esto podría no ser cierto, y luego escribir algunas pruebas unitarias que se dirijan a esas situaciones.
Por ejemplo, digamos que tienes una
Set
clase personalizada . Dos conjuntos son iguales si contienen los mismos elementos, pero es posible que las estructuras de datos subyacentes de dos conjuntos iguales difieran si esos elementos se almacenan en un orden diferente. Por ejemplo:MySet s1 = new MySet( new String[]{"Hello", "World"} ); MySet s2 = new MySet( new String[]{"World", "Hello"} ); assertEquals(s1, s2); assertTrue( s1.hashCode()==s2.hashCode() );
En este caso, el orden de los elementos en los conjuntos puede afectar su hash, según el algoritmo de hash que haya implementado. Entonces, este es el tipo de prueba que escribiría, ya que prueba el caso en el que sé que sería posible que algún algoritmo de hash produzca resultados diferentes para dos objetos que he definido como iguales.
Debería utilizar un estándar similar con su propia clase personalizada, sea lo que sea.
fuente
Vale la pena usar los complementos de junit para esto. Consulte la clase EqualsHashCodeTestCase http://junit-addons.sourceforge.net/ . Puede extender esto e implementar createInstance y createNotEqualInstance, esto verificará que los métodos equals y hashCode sean correctos.
fuente
Recomendaría EqualsTester de GSBase. Hace básicamente lo que quieres. Sin embargo, tengo dos problemas (menores) con él:
fuente
[En el momento de escribir este artículo, se publicaron otras tres respuestas].
Para reiterar, el objetivo de mi pregunta es encontrar casos estándar de pruebas para confirmar que
hashCode
yequals
están de acuerdo entre sí. Mi enfoque para esta pregunta es imaginar los caminos comunes que toman los programadores al escribir las clases en cuestión, es decir, datos inmutables. Por ejemplo:equals()
sin escribirhashCode()
. Esto a menudo significa que la igualdad se definió como la igualdad de los campos de dos instancias.hashCode()
sin escribirequals()
. Esto puede significar que el programador buscaba un algoritmo hash más eficiente.En el caso del # 2, el problema me parece inexistente. No se han realizado instancias adicionales
equals()
, por lo que no se requieren instancias adicionales para tener códigos hash iguales. En el peor de los casos, el algoritmo hash puede producir un rendimiento más deficiente para los mapas hash, lo que está fuera del alcance de esta pregunta.En el caso del # 1, la prueba unitaria estándar implica la creación de dos instancias del mismo objeto con los mismos datos pasados al constructor y la verificación de códigos hash iguales. ¿Qué pasa con los falsos positivos? Es posible elegir los parámetros del constructor que dan como resultado códigos hash iguales en un algoritmo que, sin embargo, no es sólido. Una prueba unitaria que tiende a evitar tales parámetros cumpliría el espíritu de esta pregunta. El atajo aquí es inspeccionar el código fuente
equals()
, pensar detenidamente y escribir una prueba basada en eso, pero si bien esto puede ser necesario en algunos casos, también puede haber pruebas comunes que detectan problemas comunes, y tales pruebas también cumplen con el espíritu de esta pregunta.Por ejemplo, si la clase que se va a probar (llamémosla Datos) tiene un constructor que toma un String, y las instancias construidas a partir de Strings son
equals()
instancias que lo fueronequals()
, entonces una buena prueba probablemente probaría:new Data("foo")
new Data("foo")
Incluso podríamos verificar el código hash para
new Data(new String("foo"))
obligar a la cadena a no ser internada, aunque es más probable que produzca un código hash correcto queData.equals()
un resultado correcto, en mi opinión.La respuesta de Eli Courtwright es un ejemplo de pensar mucho en una forma de romper el algoritmo hash basado en el conocimiento de la
equals
especificación. El ejemplo de una colección especial es bueno, yaCollection
que los mensajes de correo electrónico creados por el usuario aparecen a veces y son bastante propensos a fallar en el algoritmo hash.fuente
Este es uno de los únicos casos en los que tendría múltiples afirmaciones en una prueba. Dado que necesita probar el método equals, también debe verificar el método hashCode al mismo tiempo. Entonces, en cada uno de sus casos de prueba de métodos iguales, verifique también el contrato 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());
Y, por supuesto, comprobar si hay un buen código hash es otro ejercicio. Honestamente, no me molestaría en hacer una doble verificación para asegurarme de que el hashCode no cambiara en la misma ejecución, ese tipo de problema se maneja mejor al detectarlo en una revisión de código y ayudar al desarrollador a comprender por qué esa no es una buena manera para escribir métodos hashCode.
fuente
También puede usar algo similar a http://code.google.com/p/guava-libraries/source/browse/guava-testlib/src/com/google/common/testing/EqualsTester.java para probar iguales y hashCode.
fuente
Si tengo una clase
Thing
, como la mayoría de las demás, escribo una claseThingTest
, que contiene todas las pruebas unitarias para esa clase. Cada unoThingTest
tiene un métodopublic static void checkInvariants(final Thing thing) { ... }
y si la
Thing
clase anula hashCode y es igual, tiene un métodopublic static void checkInvariants(final Thing thing1, Thing thing2) { ObjectTest.checkInvariants(thing1, thing2); ... invariants that are specific to Thing }
Ese método es responsable de verificar todos los invariantes que están diseñados para mantenerse entre cualquier par de
Thing
objetos. ElObjectTest
método al que delega es responsable de verificar todos los invariantes que deben mantenerse entre cualquier par de objetos. Comoequals
yhashCode
son métodos de todos los objetos, ese método comprueba quehashCode
yequals
son coherentes.Luego tengo algunos métodos de prueba que crean pares de
Thing
objetos y los pasan alcheckInvariants
método por pares . Utilizo la partición de equivalencia para decidir qué pares vale la pena probar. Por lo general, creo que cada par sea diferente en un solo atributo, más una prueba que prueba dos objetos equivalentes.A veces también tengo un
checkInvariants
método de 3 argumentos , aunque encuentro que es menos útil para encontrar defectos, así que no hago esto a menudofuente