web-dev-qa-db-fra.com

Comment est-ce que j'affirme l'égalité sur deux classes sans une méthode égale?

Supposons que je possède une classe sans méthode equals (), à laquelle n’a pas le source. Je veux affirmer l'égalité sur deux instances de cette classe.

Je peux faire plusieurs affirmations:

assertEquals(obj1.getFieldA(), obj2.getFieldA());
assertEquals(obj1.getFieldB(), obj2.getFieldB());
assertEquals(obj1.getFieldC(), obj2.getFieldC());
...

Je n'aime pas cette solution parce que je ne comprends pas parfaitement la situation en matière d'égalité si une affirmation précoce échoue.

Je peux manuellement comparer et suivre le résultat:

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);

Cela me donne une image complète de l'égalité, mais c'est maladroit (et je n'ai même pas expliqué les éventuels problèmes nuls). Une troisième option consiste à utiliser le comparateur, mais compareTo () ne me dira pas quels champs ont échoué.

Existe-t-il une meilleure pratique pour obtenir ce que je veux de l’objet, sans sous-classification ni surtension égale à (ugh)?

70
Ryan Nelson

Mockito propose une réflexion-matcher:

Assert.assertThat(expected, new ReflectionEquals(actual, excludeFields));
44
felvhage

J'implémente généralement cette cas d'utilisation en utilisant org.Apache.commons.lang3.builder.EqualsBuilder

Assert.assertTrue(EqualsBuilder.reflectionEquals(expected,actual));
35
Abhijeet Kushe

Il y a beaucoup de réponses correctes ici, mais je voudrais aussi ajouter ma version. Ceci est basé sur Assertj.

import static org.assertj.core.api.Assertions.assertThat;

public class TestClass {

    public void test() {
        // do the actual test
        assertThat(actualObject)
            .isEqualToComparingFieldByFieldRecursively(expectedObject);
    }
}
22
Thomas

Je sais que c'est un peu vieux, mais j'espère que ça aide.

Je rencontre le même problème que vous. Après enquête, j’ai trouvé peu de questions semblables à celle-ci et, une fois la solution trouvée, je répondais de la même façon car je pensais que c’était possible d’aider les autres.

La réponse la plus votée (et non celle choisie par l'auteur) de cette question similaire , est la solution la plus appropriée pour vous.

En gros, cela consiste à utiliser la bibliothèque appelée Unitils .

C'est l'usage:

User user1 = new User(1, "John", "Doe");
User user2 = new User(1, "John", "Doe");
assertReflectionEquals(user1, user2);

Ce qui passera même si la classe User n'implémente pas equals(). Vous pouvez voir plus d'exemples et une assertion vraiment cool appelée assertLenientEquals dans leur tutorial .

10
lopezvit

Vous pouvez utiliser Apache commons lang ReflectionToStringBuilder

Vous pouvez spécifier les attributs que vous souhaitez tester un par un ou, mieux, exclure ceux que vous ne souhaitez pas:

String s = new ReflectionToStringBuilder(o, ToStringStyle.SHORT_PREFIX_STYLE)
                .setExcludeFieldNames(new String[] { "foo", "bar" }).toString()

Vous comparez ensuite les deux chaînes comme d'habitude. Pour ce qui est de la lenteur de la réflexion, je suppose que ce n’est que pour les tests, donc ne devrait pas être aussi important.

6
Matthew Farwell

Si vous utilisez hamcrest pour vos assertions (assertThat) et que vous ne souhaitez pas extraire de bibliothèques de test supplémentaires, vous pouvez utiliser SamePropertyValuesAs.samePropertyValuesAs pour affirmer des éléments qui n'ont pas de méthode equals remplacée. 

L’avantage, c’est que vous n’aurez pas besoin d’insérer un autre framework de test et que cela produira une erreur utile lorsque l’assertion échouera (expected: field=<value> but was field=<something else>) au lieu de expected: true but was false si vous utilisez quelque chose comme EqualsBuilder.reflectionEquals().

L'inconvénient est que la comparaison est superficielle et qu'il n'y a pas d'option pour exclure des champs (comme dans EqualsBuilder). Vous devrez donc vous en servir pour imbriquer des objets imbriqués (par exemple, supprimez-les et comparez-les indépendamment).

Meilleur cas:

import static org.hamcrest.beans.SamePropertyValuesAs.samePropertyValuesAs;
...
assertThat(actual, is(samePropertyValuesAs(expected)));

Affaire moche:

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)));

Alors, choisis ton poison. Cadre supplémentaire (par exemple, Unitils), erreur inutile (par exemple, EqualsBuilder) ou comparaison superficielle (hamcrest).

4
Marquee

La bibliothèque Hamcrest 1.3 Utility Matchers a un adaptateur spécial qui utilise la réflexion au lieu d’égal.

assertThat(obj1, reflectEquals(obj2));
3
Stefan Birkner

Certaines des méthodes de comparaison de réflexion sont peu profondes

Une autre option consiste à convertir l'objet en json et à comparer les chaînes.

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))
0
Avraham Shalev

Il s'agit d'une méthode de comparaison générique, qui compare deux objets d'une même classe à ses valeurs de champs (gardez à l'esprit que celles-ci sont accessibles par la méthode 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 ;
    }
}
0
Shantonu

Vous pouvez utiliser la réflexion pour "automatiser" le test d'égalité complet. vous pouvez implémenter le code de "suivi" que vous avez écrit pour un seul champ, puis utiliser la réflexion pour exécuter ce test sur tous les champs de l'objet.

0
jtahlborn

Je suis tombé sur un cas très similaire.

Je voulais comparer à un test qu'un objet avait les mêmes valeurs d'attribut qu'un autre, mais des méthodes comme is(), refEq(), etc. ne fonctionneraient pas pour des raisons telles que mon objet a une valeur nulle dans son attribut id.

C'est donc la solution que j'ai trouvée (enfin, un collègue a trouvé):

import static org.Apache.commons.lang.builder.CompareToBuilder.reflectionCompare;

assertThat(reflectionCompare(expectedObject, actualObject, new String[]{"fields","to","be","excluded"}), is(0));

Si la valeur obtenue à partir de reflectionCompare est 0, cela signifie qu'elles sont égales. Si c'est -1 ou 1, ils diffèrent sur certains attributs.

0
cavpollo

En utilisant Shazamcast , vous pouvez faire:

assertThat(obj1, sameBeanAs(obj2));
0

J'avais exactement le même problème lorsque je testais une application Android, et la solution la plus simple que j'ai proposée consistait simplement à utiliser Gson pour convertir mes objets de valeur réels et attendus en json et les comparer sous forme de chaînes. 

String actual = new Gson().toJson( myObj.getValues() );
String expected = new Gson().toJson( new MyValues(true,1) );

assertEquals(expected, actual);

L'avantage de cette comparaison manuelle par rapport à chaque champ est que vous comparez tous vos champs. Ainsi, même si vous ajoutez ultérieurement un nouveau champ à votre classe, il sera automatiquement testé, par rapport à si vous utilisiez un groupe de assertEquals() sur tous les champs, qui devront ensuite être mis à jour si vous ajoutez d'autres champs à votre classe.

jUnit affiche également les chaînes pour vous afin que vous puissiez voir directement où elles diffèrent. Vous ne savez pas si le classement des champs par Gson est fiable, cela pourrait être un problème potentiel.

0
Magnus W

Comparez champ par champ:

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);
0
EthanB

J'ai essayé toutes les réponses et rien ne fonctionnait vraiment pour moi. 

J'ai donc créé ma propre méthode qui compare des objets Java simples sans approfondir les structures imbriquées ... 

La méthode retourne NULL si tous les champs correspondent ou une chaîne contenant des détails incompatibles. 

Seules les propriétés ayant une méthode getter sont comparées.

Comment utiliser

        assertNull(TestUtils.diff(obj1,obj2,ignore_field1, ignore_field2));

Exemple de sortie en cas d'incompatibilité

La sortie affiche les noms de propriété et les valeurs respectives des objets comparés

alert_id(1:2), city(Moscow:London)

Code (Java 8 et supérieur):

 public static String diff(Object x1, Object x2, String ... ignored) throws Exception{
        final StringBuilder response = new StringBuilder();
        for (Method m:Arrays.stream(x1.getClass().getMethods()).filter(m->m.getName().startsWith("get")
        && m.getParameterCount()==0).collect(toList())){

            final String field = m.getName().substring(3).toLowerCase();
            if (Arrays.stream(ignored).map(x->x.toLowerCase()).noneMatch(ignoredField->ignoredField.equals(field))){
                Object v1 = m.invoke(x1);
                Object v2 = m.invoke(x2);
                if ( (v1!=null && !v1.equals(v2)) || (v2!=null && !v2.equals(v1))){
                    response.append(field).append("(").append(v1).append(":").append(v2).append(")").append(", ");
                }
            }
        }
        return response.length()==0?null:response.substring(0,response.length()-2);
    }
0
Stan Sokolov

Dans les cas courants avec AssertJ, vous pouvez créer une stratégie de comparaison personnalisée:

assertThat(frodo).usingComparator(raceComparator).isEqualTo(sam)
assertThat(fellowshipOfTheRing).usingElementComparator(raceComparator).contains(sauron);

Utilisation d'une stratégie de comparaison personnalisée dans les assertions

Exemples AssertJ

0
GKislin