it-swarm.com.de

Wie behaupte ich Gleichheit in zwei Klassen ohne Equals-Methode?

Angenommen, ich habe eine Klasse ohne equals () -Methode, zu der die Quelle nicht gehört. Ich möchte Gleichheit in zwei Instanzen dieser Klasse geltend machen.

Ich kann mehrere Zusicherungen machen:

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

Ich mag diese Lösung nicht, weil ich nicht das vollständige Gleichheitsbild bekomme, wenn eine frühe Zusicherung fehlschlägt.

Ich kann manuell selbst vergleichen und das Ergebnis verfolgen:

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

Dies gibt mir das vollständige Gleichheitsbild, ist aber klobig (und ich habe nicht einmal mögliche Nullprobleme berücksichtigt). Eine dritte Option ist die Verwendung von Comparator, aber CompareTo () sagt mir nicht, bei welchen Feldern die Gleichheit fehlgeschlagen ist.

Gibt es eine bessere Praxis, um das, was ich will, aus dem Objekt herauszuholen, ohne dabei gleich (ig) zu überschreiben und zu überschreiben?

70
Ryan Nelson

Mockito bietet einen Reflection-Matcher an:

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

Im Allgemeinen implementiere ich diese Anwendung mit org.Apache.commons.lang3.builder.EqualsBuilder

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

Hier gibt es viele richtige Antworten, aber ich möchte auch meine Version hinzufügen. Dies basiert auf 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

Ich weiß, dass es ein bisschen alt ist, aber ich hoffe es hilft.

Ich habe das gleiche Problem wie Sie, also habe ich nach der Untersuchung wenig ähnliche Fragen als diese gefunden, und nachdem ich die Lösung gefunden habe, beantworte ich dasselbe, da ich dachte, es könnte anderen helfen.

Die am meisten gewählte Antwort (nicht die vom Autor gewählte) dieser ähnlichen Frage , ist die am besten geeignete Lösung für Sie.

Grundsätzlich besteht die Verwendung der Bibliothek Unitils .

Dies ist die Verwendung:

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

Was passieren wird, auch wenn die Klasse Userequals() nicht implementiert. Weitere Beispiele und eine wirklich coole Zusicherung namens assertLenientEquals finden Sie in ihrem Tutorial .

10
lopezvit

Sie können Apache commons lang ReflectionToStringBuilder verwenden

Sie können entweder die Attribute, die Sie testen möchten, einzeln angeben oder besser diejenigen ausschließen, die Sie nicht möchten:

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

Sie vergleichen dann die beiden Zeichenfolgen wie üblich. Für den Punkt, dass Reflexion langsam ist, nehme ich an, dass dies nur für Tests ist und daher nicht so wichtig sein sollte.

6
Matthew Farwell

Wenn Sie hamcrest für Ihre Asserts (assertThat) verwenden und keine zusätzlichen Test-Bibliotheken einlesen möchten, können Sie SamePropertyValuesAs.samePropertyValuesAs verwenden, um Elemente zu bestätigen, die keine überschriebene equals-Methode haben. 

Der Vorteil ist, dass Sie kein weiteres Test-Framework einspielen müssen, und es wird ein nützlicher Fehler ausgegeben, wenn die Bestätigung fehlschlägt (expected: field=<value> but was field=<something else>) anstelle von expected: true but was false, wenn Sie etwas wie EqualsBuilder.reflectionEquals() verwenden.

Der Nachteil ist, dass es sich um einen flachen Vergleich handelt und es keine Möglichkeit gibt, Felder auszuschließen (wie in EqualsBuilder). Sie müssen also geschachtelte Objekte umgehen (z. B. entfernen und unabhängig voneinander vergleichen).

I'm besten fall:

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

Hässlicher Fall:

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

Also, nimm dein Gift. Zusätzlicher Rahmen (z. B. Unitils), nicht hilfreicher Fehler (z. B. EqualsBuilder) oder flacher Vergleich (hamcrest).

4
Marquee

Die Bibliothek Hamcrest 1.3 Utility Matchers hat einen speziellen Matcher, der Reflections anstelle von Equals verwendet.

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

Einige der Reflexionsvergleichsmethoden sind flach

Eine weitere Option besteht darin, das Objekt in einen Json zu konvertieren und die Zeichenfolgen zu vergleichen.

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

Hierbei handelt es sich um eine generische Vergleichsmethode, bei der zwei Objekte derselben Klasse nach ihren Feldwerten verglichen werden.

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

Sie können Reflection verwenden, um die vollständige Gleichheitsprüfung zu "automatisieren". Sie können den Gleichheits- "Tracking" -Code, den Sie für ein einzelnes Feld geschrieben haben, implementieren. Anschließend können Sie diesen Test für alle Felder im Objekt mit Reflektion ausführen.

0
jtahlborn

Ich bin auf einen sehr ähnlichen Fall gestoßen.

Ich wollte bei einem Test vergleichen, dass ein Objekt dieselben Attributwerte wie ein anderes hatte, aber Methoden wie is(), refEq() usw. würden aus Gründen nicht funktionieren, da mein Objekt einen Nullwert in seinem id-Attribut hat.

Das war also die Lösung, die ich gefunden habe (naja, ein Kollege fand):

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

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

Wenn der von reflectionCompare erhaltene Wert 0 ist, bedeutet dies, dass sie gleich sind. Wenn es -1 oder 1 ist, unterscheiden sie sich in einigen Attributen.

0
cavpollo

Mit Shazamcast können Sie Folgendes tun:

assertThat(obj1, sameBeanAs(obj2));

Ich hatte genau das gleiche Rätsel beim Gerätetest einer Android-App, und die einfachste Lösung, die ich fand, bestand einfach darin, Gson zu verwenden, um meine tatsächlichen und erwarteten Wertobjekte in json zu konvertieren und sie als Zeichenketten zu vergleichen. 

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

assertEquals(expected, actual);

Der Vorteil beim manuellen Vergleichen von Feld für Feld besteht darin, dass Sie alle Ihre Felder vergleichen. Wenn Sie also später ein neues Feld zu Ihrer Klasse hinzufügen, wird es automatisch getestet, verglichen mit der Verwendung von eine Reihe von assertEquals() in allen Feldern, die dann aktualisiert werden müssten, wenn Sie Ihrer Klasse weitere Felder hinzufügen.

jUnit zeigt auch die Zeichenfolgen an, damit Sie direkt sehen können, wo sie sich unterscheiden. Nicht sicher, wie zuverlässig die Feldreihenfolge durch Gson ist, könnte dies ein potenzielles Problem sein.

0
Magnus W

Feld für Feld vergleichen:

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

Ich habe alle Antworten ausprobiert und nichts hat für mich funktioniert. 

Also habe ich meine eigene Methode entwickelt, die einfache Java-Objekte vergleicht, ohne tief in verschachtelte Strukturen zu gehen ... 

Die Methode gibt null zurück, wenn alle Felder übereinstimmen oder eine Zeichenfolge enthält, die nicht übereinstimmende Details enthält. 

Es werden nur Eigenschaften verglichen, die über eine Getter-Methode verfügen.

Wie benutzt man

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

Beispielausgabe bei Nichtübereinstimmung

Ausgabe zeigt Eigenschaftsnamen und entsprechende Werte von verglichenen Objekten

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

Code (Java 8 und höher):

 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

Im Allgemeinen können Sie mit AssertJ eine benutzerdefinierte Vergleichsstrategie erstellen:

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

Verwenden einer benutzerdefinierten Vergleichsstrategie in Assertions

AssertJ Beispiele

0
GKislin