2

Estoy usando Java 8, Netbeans 8.2 y JUnit 4.12.

Tengo dos instancias de una clase que quiero comparar para saber si son "iguales", donde sus propiedades tienen los mismos valores. Es decir que si tienen id y nombre, si escribo en el test:

assertEquals(instanceA.getId(), instanceB.getId( ));
assertEquals(instanceA.getName(), instanceB.getName( ));

pasa OK, pero si añado un tercer assert comparándolos directamente:

assertEquals(instanceA, instanceB);

o bien

assertTrue(instanceA.equals(instanceB));

este falla. Creo que porque aunque guardan lo mismo son instancias diferentes (apuntan a diferentes direcciones de memoria). ¿Alguien puede aclararlo? Gracias.

Editado

La pregunta que se sugiere como "duplicado" se centra en comparar el valor de dos Strings y todas las respuestas van en este sentido, excepto esta: https://es.stackoverflow.com/a/42937/20709 que hace una comparación de objetos, obviando esta respuesta, todas las demás soluciones fallan. Mi pregunta se refiere a cómo comparar objetos de acuerdo al valor de sus propiedades y en concreto de cara a implementar test unitarios. Creo que revisando ambas preguntas y sus respuestas pueden verse similitudes pero está claro que son dos preguntas diferentes.

Tal vez no me explique correctamente. Si alguien no lo tiene claro, ¿si es la misma pregunta como tiene una solución completamente diferente?

Comparar dos strings:

String a = "hola";
String b = "hola";
boolean res = a.equals(b);
System.out.println("Comparando strings: " + res); 
// Comparando strings: true

Comparar dos objetos:

class Foo {
    private int id;
    private String name;

    public Foo(int id, String name) {
        this.id = id;
        this.name = name;
    }

} // class

Foo a = new Foo(1, "hola");
Foo b = new Foo(1, "hola");
boolean res = a.equals(b);
System.out.println("Comparando objetos: " + res); 
// Comparando objetos: false

¿Qué ha pasado? Pues eso es lo que estaba preguntando básicamente...

Orici
  • 7,269
  • 7
  • 35
  • 68
  • 4
    Cuando has dicho que "apuntan a diferentes direciones de memoria" has respondido a tu propia pregunta. – NaCl Jul 18 '18 at 13:56
  • 1
    Debes hacer tu propia implementacion de equals para poder comparar tus propios objetos. Algunos IDE's te lo generan automáticamente. – Iker Obregon Reigosa Jul 18 '18 at 14:21

3 Answers3

5

El método equals de Object es simplemente una comparación

public boolean equals(Object obj) {
    return (this == obj);
}

Por lo que si quieres que sean iguales (aún apuntando a diferente localización de memoria) debes sobreescribir el método equals y si ese objeto lo usarás en algun Collection con hash, sobreescribe de una vez el hashCode

Autogenerar equals

En caso de que sea imposible editar la clase por ser clase Legacy, no tengas el source o demás existe la biblioteca de test

Dependencia en Maven

<dependency>
    <groupId>org.unitils</groupId>
    <artifactId>unitils-core</artifactId>
    <version>3.4.2</version>
    <scope>test</scope>
</dependency>

y cambiar en tu código de

assertEquals(instanceA, instanceB);

a

assertReflectionEquals(instanceA, instanceB);

La documentación y explicación viene en su página oficial http://www.unitils.org/tutorial-reflectionassert.html

Ajeno
  • 1,073
  • 7
  • 14
  • Gracias x tu respuesta. Había visto esta posibilidad antes de plantear la pregunta pero no quería añadir código a las clases solo para poder testearlas, en este sentido puede ser más interesante probar el framework que mencionas. – Orici Jul 18 '18 at 21:44
  • Efectivamente sobrescribe el método Equals y utiliza los atributos con los que consideras suficiente para que un objeto sea igual a otro.Para un usuario por ejemplo (id, nombre) pero el listado de direcciones no sería necesario ya que hay elementos de mayor peso que evitarían tener que llegar a comparar este. – UHDante Jul 19 '18 at 07:42
2

Tienes que sobreescribir el método

equals(Object obj)

Ahi puedes comparar los atributos que tu estimes necesarios para determinar de que dos objetos son iguales.

Lo que hace Java por defecto es comparar la referencia para determinar si dos objetos son iguales.

Dale un vistazo en la documentación para ver las propiedades que tiene que cubrir tu método equals.

https://docs.oracle.com/javase/7/docs/api/java/lang/Object.html#equals(java.lang.Object)

rfrp
  • 409
  • 2
  • 9
  • Gracias x tu respuesta. Había visto esta posibilidad antes de plantear la pregunta pero no quería añadir código a las clases solo para poder testearlas. – Orici Jul 18 '18 at 21:43
1

Una alternativa para no tocar el código de las clases del proyecto y poder usar JUnit sin tener que hacer múltiples asserts es sobreescribir el método equals() dentro de una nueva clase, creada sólo para las pruebas.

Creo una clase XxxMock que extiende de la clase Xxx a testear y la situó junto a las clases con los test, en esta clase XxxMock sólo se añaden los constructores necesarios y el método equals() sobreescrito.

Así para testear la siguiente clase:

public class Foo {

    private String id;
    private String name;
    private int age;


    public Foo(String id, String name, int age) {
        this.id   = id;
        this.name = name;
        this.age  = age;
    }

    // Setters and Getters
    // ...

} // class

Antes necesitaba un assert para comprobar cada una de las propiedades:

assertEquals(expResult.getId(),   result.getId( ));
assertEquals(expResult.getName(), result.getName( ));
assertEquals(expResult.getDescription(), result.getDescription( ));

Ahora, creo la siguiente clase y la situó con las clases que contienen las pruebas:

public class FooMock extends Foo {

    public FooMock(String id, String name, int age) {
        super(id, name, age);
    }


    /**
     *
     * @param obj
     * @return
     */
    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }

        Foo foo = (Foo) obj;

        return (
            ((this.getId() == null && foo.getId() == null) ||
                this.getId().equals(foo.getId( ))) &&
            ((this.getName() == null && foo.getName() == null) ||
                this.getName().equals(foo.getName( ))) &&
            this.getAge() == foo.getAge( ));
    }

} // class

Ahora "expResult" deberá ser una instancia de FooMock en lugar de una de Foo y sólo será necesario un assert:

assertTrue(expResult.equals(result));

Editado:

Se comprueba si obj es nulo dentro de equals().

Orici
  • 7,269
  • 7
  • 35
  • 68
  • Es una alternativa que en lo personal no recomiendo, preferiría editar el código de la clase Foo (si tengo acceso), por que al ser un POJO y se modifica, está casado a modificar la clase de test, algo que en lo personal no me gusta, además en tu ejemplo tienes un potencial NPE no validas si obj es nulo. – Ajeno Jul 19 '18 at 18:51
  • @Ajeno no estoy modificando los test, los estoy creando. La pregunta original esta encaminada a encontrar la mejor manera de implementar los test: como hacer la comparación de objetos. Cierto lo de la NPE, edite el código. Saludos. – Orici Jul 19 '18 at 21:39
  • Si entiendo el punto de que estas creandolo, sin embargo a lo que me refiero, es cuando la clase Foo sea modificada por que se modificó el requerimiento, deberás si o sí modificar la clase Test y es lo que no estoy de acuerdo, (si agregas un parámetro en constructor, si añades un nuevo Atributo, etc). Por lo que será doble mantenimiento, pero es solo mi pensar. – Ajeno Jul 19 '18 at 21:43
  • 1
    @Ajeno estamos de acuerdo. Al cambiar *Foo* por nuevos requerimientos, se deberán modificar los tests y en su caso *FooMock*, que yo entiendo como parte de estos tests, cuando esos nuevos requerimientos afecten a las propiedades a comparar. Entiendo tu punto de vista, pero es normal tener que cambiar las pruebas cuando cambiamos **el funcionamiento** de las clases que se prueban, otra cosa sería tener que cambiar las pruebas si refactorizamos las clases, denotaría un mal diseño de las pruebas. En todo caso tengo poca experiencia haciendo pruebas, mi solución es más por lógica (mejor o peor). – Orici Jul 19 '18 at 23:04