java - programa - ¿Cómo afirmo la igualdad en dos clases sin un método igual?
programa de calificaciones en java (18)
Digamos que tengo una clase sin método equals (), a la cual no tengo la fuente. Quiero afirmar la igualdad en dos instancias de esa clase.
Puedo hacer múltiples aseveraciones:
assertEquals(obj1.getFieldA(), obj2.getFieldA());
assertEquals(obj1.getFieldB(), obj2.getFieldB());
assertEquals(obj1.getFieldC(), obj2.getFieldC());
...
No me gusta esta solución porque no obtengo la imagen completa de igualdad si falla una afirmación temprana.
Puedo comparar de forma manual por mi cuenta y seguir el resultado:
String errorStr = "";
if(!obj1.getFieldA().equals(obj2.getFieldA())) {
errorStr += "expected: " + obj1.getFieldA() + ", actual: " + obj2.getFieldA() + "/n";
}
if(!obj1.getFieldB().equals(obj2.getFieldB())) {
errorStr += "expected: " + obj1.getFieldB() + ", actual: " + obj2.getFieldB() + "/n";
}
...
assertEquals("", errorStr);
Esto me da la imagen completa de igualdad, pero es torpe (y ni siquiera he tenido en cuenta posibles problemas nulos). Una tercera opción es usar Comparator, pero compareTo () no me dirá qué campos fallaron la igualdad.
¿Hay una mejor práctica para obtener lo que quiero del objeto, sin subclases y anulación de iguales (ugh)?
¿Puedes poner el código de comparación que publicaste en algún método de utilidad estático?
public static String findDifference(Type obj1, Type obj2) {
String difference = "";
if (obj1.getFieldA() == null && obj2.getFieldA() != null
|| !obj1.getFieldA().equals(obj2.getFieldA())) {
difference += "Difference at field A:" + "obj1 - "
+ obj1.getFieldA() + ", obj2 - " + obj2.getFieldA();
}
if (obj1.getFieldB() == null && obj2.getFieldB() != null
|| !obj1.getFieldB().equals(obj2.getFieldB())) {
difference += "Difference at field B:" + "obj1 - "
+ obj1.getFieldB() + ", obj2 - " + obj2.getFieldB();
// (...)
}
return difference;
}
Entonces puedes usar este método en JUnit como este:
assertEquals ("Los objetos no son iguales", "", findDifferences (obj1, obj));
que no es torpe y le da información completa sobre las diferencias, si existen (a través de no exactamente en forma normal de assertEqual, pero obtiene toda la información, por lo que debería ser bueno).
Algunos de los métodos de comparación de reflexión son superficiales
Otra opción es convertir el objeto a json y comparar las cadenas.
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public static String getJsonString(Object obj) {
try {
ObjectMapper objectMapper = new ObjectMapper();
return bjectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(obj);
} catch (JsonProcessingException e) {
LOGGER.error("Error parsing log entry", e);
return null;
}
}
...
assertEquals(getJsonString(MyexpectedObject), getJsonString(MyActualObject))
Aquí hay muchas respuestas correctas, pero me gustaría agregar mi versión también. Esto se basa en Assertj.
import static org.assertj.core.api.Assertions.assertThat;
public class TestClass {
public void test() {
// do the actual test
assertThat(actualObject)
.isEqualToComparingFieldByFieldRecursively(expectedObject);
}
}
Comparar campo por campo:
assertNotNull("Object 1 is null", obj1);
assertNotNull("Object 2 is null", obj2);
assertEquals("Field A differs", obj1.getFieldA(), obj2.getFieldA());
assertEquals("Field B differs", obj1.getFieldB(), obj2.getFieldB());
...
assertEquals("Objects are not equal.", obj1, obj2);
De sus comentarios a otras respuestas, no entiendo lo que quiere.
Solo por el bien de la discusión, digamos que la clase anuló el método de iguales.
Entonces su UT se verá algo así como:
SomeType expected = // bla
SomeType actual = // bli
Assert.assertEquals(expected, actual).
Y has terminado. Además, no puede obtener la "imagen completa de igualdad" si la afirmación falla.
Por lo que entiendo, estás diciendo que incluso si el tipo reemplaza a iguales, no te interesaría, ya que quieres obtener la "imagen completa de igualdad". Por lo tanto, no tiene sentido ampliar y anular iguales.
Entonces tienes que elegir opciones: comparar propiedad por propiedad, usar reflexión o verificaciones codificadas, sugeriría lo último. O bien: compare las representaciones legibles de estos objetos.
Por ejemplo, puede crear una clase auxiliar que serialice el tipo que desea comparar a un documento XML y que compare el XML resultante. en este caso, puede ver visualmente qué es exactamente igual y qué no.
Este enfoque le dará la oportunidad de ver la imagen completa, pero también es relativamente engorroso (y un poco propenso a errores al principio).
En el caso común con AssertJ puede crear una estrategia de comparación personalizada:
assertThat(frodo).usingComparator(raceComparator).isEqualTo(sam)
assertThat(fellowshipOfTheRing).usingElementComparator(raceComparator).contains(sauron);
Usar una estrategia de comparación personalizada en aserciones
Este es un método de comparación genérico, que compara dos objetos de una misma clase para sus valores de campos (tenga en cuenta que son accesibles mediante el método get)
public static <T> void compare(T a, T b) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
AssertionError error = null;
Class A = a.getClass();
Class B = a.getClass();
for (Method mA : A.getDeclaredMethods()) {
if (mA.getName().startsWith("get")) {
Method mB = B.getMethod(mA.getName(),null );
try {
Assert.assertEquals("Not Matched = ",mA.invoke(a),mB.invoke(b));
}catch (AssertionError e){
if(error==null){
error = new AssertionError(e);
}
else {
error.addSuppressed(e);
}
}
}
}
if(error!=null){
throw error ;
}
}
Esto no ayudará al OP, pero podría ayudar a cualquier desarrollador de C # que termine aquí ...
Al igual que Enrique publicó , debes anular el método de iguales.
¿Hay una mejor práctica para obtener lo que quiero del objeto, sin subclases y anulación de iguales (ugh)?
Mi sugerencia es no usar una subclase. Usa una clase parcial
Definiciones parciales de clase (MSDN)
Entonces tu clase se vería como ...
public partial class TheClass
{
public override bool Equals(Object obj)
{
// your implementation here
}
}
Para Java, estoy de acuerdo con la sugerencia de usar la reflexión. Solo recuerda que debes evitar usar el reflejo siempre que sea posible. Es lento, difícil de depurar, y aún más difícil de mantener en el futuro porque los IDE pueden romper su código haciendo un cambio de nombre de campo o algo así. ¡Ten cuidado!
Generalmente implemento este uso usando org.apache.commons.lang3.builder.EqualsBuilder
Assert.assertTrue(EqualsBuilder.reflectionEquals(expected,actual));
La biblioteca Hamcrest 1.3 Utility Matchers tiene un matcher especial que utiliza la reflexión en lugar de iguales.
assertThat(obj1, reflectEquals(obj2));
Me encontré con un caso muy similar.
Quería comparar en una prueba que un objeto tenía los mismos valores de atributo que otro, pero métodos como is()
, refEq()
, etc. no funcionarían por razones como que mi objeto tenga un valor nulo en su atributo id
.
Así que esta fue la solución que encontré (bueno, un compañero de trabajo encontrado):
import static org.apache.commons.lang.builder.CompareToBuilder.reflectionCompare;
assertThat(reflectionCompare(expectedObject, actualObject, new String[]{"fields","to","be","excluded"}), is(0));
Si el valor obtenido de reflectionCompare
es 0, significa que son iguales. Si es -1 o 1, difieren en algún atributo.
Puede anular el método equals de la clase como:
@Override
public int hashCode() {
int hash = 0;
hash += (app != null ? app.hashCode() : 0);
return hash;
}
@Override
public boolean equals(Object object) {
HubRule other = (HubRule) object;
if (this.app.equals(other.app)) {
boolean operatorHubList = false;
if (other.operator != null ? this.operator != null ? this.operator
.equals(other.operator) : false : true) {
operatorHubList = true;
}
if (operatorHubList) {
return true;
} else {
return false;
}
} else {
return false;
}
}
Bueno, si quieres comparar dos objetos de una clase, debes implementar de alguna manera los iguales y el método del código hash
Puede usar Apache commons lang ReflectionToStringBuilder
Puede especificar los atributos que desea probar uno por uno, o mejor, excluir aquellos que no desea:
String s = new ReflectionToStringBuilder(o, ToStringStyle.SHORT_PREFIX_STYLE)
.setExcludeFieldNames(new String[] { "foo", "bar" }).toString()
Luego, compara las dos cuerdas como siempre. Para el punto de que la reflexión es lenta, supongo que esto es solo para probar, por lo que no debería ser tan importante.
Puede usar la reflexión para "automatizar" la prueba de igualdad completa. puede implementar el código de "seguimiento" de igualdad que escribió para un solo campo, luego use el reflejo para ejecutar esa prueba en todos los campos del objeto.
Sé que es un poco viejo, pero espero que ayude.
Me encuentro con el mismo problema que tú, así que, después de la investigación, encontré algunas preguntas similares a esta, y, después de encontrar la solución, estoy respondiendo lo mismo en esas, ya que pensé que podría ayudar a otros.
La respuesta más elegida (no la que eligió el autor) de esta pregunta similar es la solución más adecuada para usted.
Básicamente, consiste en usar la biblioteca llamada Unitils .
Este es el uso:
User user1 = new User(1, "John", "Doe");
User user2 = new User(1, "John", "Doe");
assertReflectionEquals(user1, user2);
Que pasará incluso si el User
clase no implementa equals()
. Puede ver más ejemplos y una assertLenientEquals
realmente genial llamada assertLenientEquals
en su tutorial .
Si está utilizando hamcrest para sus afirmaciones (assertThat) y no desea obtener libs de prueba adicionales, puede usar SamePropertyValuesAs.samePropertyValuesAs
para afirmar los elementos que no tienen un método SamePropertyValuesAs.samePropertyValuesAs
reemplazado.
Lo bueno es que no tiene que recurrir a otro marco de prueba y le dará un error útil cuando la afirmación falla ( expected: field=<value> but was field=<something else>
) en lugar de expected: true but was false
si usa algo como EqualsBuilder.reflectionEquals()
.
La desventaja es que es una comparación superficial y no hay opción para excluir campos (como en EqualsBuilder), por lo que tendrás que trabajar con objetos anidados (por ejemplo, eliminarlos y compararlos de forma independiente).
Mejor caso:
import static org.hamcrest.beans.SamePropertyValuesAs.samePropertyValuesAs;
...
assertThat(actual, is(samePropertyValuesAs(expected)));
Caso feo:
import static org.hamcrest.beans.SamePropertyValuesAs.samePropertyValuesAs;
...
SomeClass expected = buildExpected();
SomeClass actual = sut.doSomething();
NestedClass expectedSubObject = expected.getSubObject();
expected.setSubObject(null);
NestedClass actualSubObject = actual.getSubObject();
actual.setSubObject(null);
assertThat(actual, is(samePropertyValuesAs(expected)));
assertThat(actualSubObject, is(samePropertyValuesAs(expectedSubObject)));
Entonces, elige tu veneno. Marco adicional (por ejemplo, Unitils), error inútil (p. Ej., EqualsBuilder) o superficial (hamcrest).
Tuve exactamente el mismo enigma al probar una unidad en una aplicación de Android, y la solución más sencilla que se me ocurrió fue simplemente usar Gson para convertir mis objetos de valor reales y esperados en json
y compararlos como cadenas.
String actual = new Gson().toJson( myObj.getValues() );
String expected = new Gson().toJson( new MyValues(true,1) );
assertEquals(expected, actual);
Las ventajas de esto sobre la comparación manual de campo por campo es que comparas todos tus campos, por lo que incluso si posteriormente agregas un nuevo campo a tu clase, se probará automáticamente, en comparación con si assertEquals()
un montón de assertEquals()
en todos los campos, que luego necesitarían actualizarse si agrega más campos a su clase.
jUnit también muestra las cadenas para que pueda ver directamente dónde difieren. Sin embargo, no estoy seguro de cuán confiable es el orden de campo de Gson
, que podría ser un problema potencial.