Подтвердить что ты не робот

Как утверждать равенство на двух классах без метода equals?

Скажем, у меня есть класс без метода equals(), для которого не существует источника. Я хочу утверждать равенство в двух экземплярах этого класса.

Я могу сделать несколько утверждений:

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

Мне не нравится это решение, потому что я не получаю полную картину равенства, если раннее утверждение терпит неудачу.

Я могу вручную сравнить себя и отслеживать результат:

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

Это дает мне полную картину равенства, но неуклюжий (и я даже не учитывал возможных нулевых проблем). Третий вариант - использовать Comparator, но compareTo() не скажет мне, какие поля не получили равенства.

Есть ли лучшая практика для получения того, что я хочу от объекта, без подкласса и переопределения равных (ugh)?

4b9b3361

Ответ 1

Mockito предлагает сопоставление отражений:

Для последней версии Mockito используйте:

Assert.assertTrue(new ReflectionEquals(expected, excludeFields).matches(actual));

Для более старых версий используйте:

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

Ответ 2

Я обычно реализую этот usecase, используя org.apache.commons.lang3.builder.EqualsBuilder

Assert.assertTrue(EqualsBuilder.reflectionEquals(expected,actual));

Ответ 3

Здесь есть много правильных ответов, но я хотел бы добавить и мою версию. Это основано на Assertj.

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

public class TestClass {

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

Ответ 4

Я знаю это немного старое, но я надеюсь, что это помогает.

Я столкнулся с той же проблемой, что и вы, поэтому после расследования я обнаружил несколько похожих вопросов, чем этот, и, найдя решение, отвечаю на те же самые, поскольку я думал, что он может помочь другим.

Ответ на этот похожий вопрос (не тот, который был выбран автором) является наиболее подходящим решением для вас.

По сути, он состоит из использования библиотеки под названием Unitils.

Это использование:

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

Который пройдет, даже если класс User не реализует equals(). Вы можете увидеть больше примеров и действительно assertLenientEquals утверждения assertLenientEquals в их руководстве.

Ответ 5

Вы можете использовать Apache Commons Lang ReflectionToStringBuilder

Вы можете указать атрибуты, которые вы хотите проверить по одному, или, что еще лучше, исключить те, которые вам не нужны:

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

Затем вы сравниваете две строки как обычно. Что касается медленного отражения, я предполагаю, что это только для тестирования, поэтому не должно быть таким важным.

Ответ 6

Если вы используете hamcrest для своих утверждений (assertThat) и не хотите использовать дополнительные тестовые библиотеки, то вы можете использовать SamePropertyValuesAs.samePropertyValuesAs, чтобы утверждать элементы, у которых нет переопределенного метода equals.

Положительным моментом является то, что вам не нужно вставлять еще одну тестовую среду, и она даст полезную ошибку при сбое подтверждения (expected: field=<value> but was field=<something else>) вместо expected: true but was false, если вы используете что-то вроде EqualsBuilder.reflectionEquals().

Недостатком является то, что это мелкое сравнение и нет возможности исключать поля (как в EqualsBuilder), поэтому вам придется работать с вложенными объектами (например, удалять их и сравнивать их независимо).

Лучший случай:

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

Гадкий случай:

import static org.hamcrest.beans.SamePropertyValuesAs.samePropertyValuesAs;
...
SomeClass expected = buildExpected(); 
SomeClass actual = sut.doSomething();

assertThat(actual.getSubObject(), is(samePropertyValuesAs(expected.getSubObject())));    
expected.setSubObject(null);
actual.setSubObject(null);

assertThat(actual, is(samePropertyValuesAs(expected)));

Итак, выбери свой яд. Дополнительная структура (например, Unitils), бесполезная ошибка (например, EqualsBuilder) или поверхностное сравнение (подколенное сухожилие).

Ответ 7

В библиотеке Hamcrest 1.3 Utility Matchers есть специальный матчи, который использует отражение вместо равных.

assertThat(obj1, reflectEquals(obj2));

Ответ 8

Используя Shazamcrest, вы можете сделать:

assertThat(obj1, sameBeanAs(obj2));

Ответ 9

Сравнение полей по полю:

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

Ответ 10

Вы можете использовать отражение, чтобы "автоматизировать" полное тестирование равенства. вы можете реализовать код отслеживания равенства, который вы написали для одного поля, а затем использовать отражение для запуска этого теста во всех полях объекта.

Ответ 11

Это общий метод сравнения, который сравнивает два объекта одного и того же класса с его значениями его полей (имейте в виду, что они доступны методом 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 ;
    }
}

Ответ 12

Я наткнулся на очень похожий случай.

Я хотел сравнить в тесте, что объект имеет те же значения атрибутов, что и другие, но такие методы, как is(), refEq() и т.д., не будут работать по таким причинам, как мой объект, имеющий нулевое значение в своем id.

Итак, это было решение, которое я нашел (ну, найден коллега):

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

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

Если значение, полученное из reflectionCompare, равно 0, значит, они равны. Если это -1 или 1, они различаются по некоторому атрибуту.

Ответ 14

Некоторые методы сравнения отражений мелкие

Другой вариант - преобразовать объект в json и сравнить строки.

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

Ответ 15

У меня была такая же загадка, когда я тестировал приложение Android, и самым простым решением, которое я придумал, было просто использовать Gson для преобразования моих реальных и ожидаемых объектов значения в json и сравнить их как строки.

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

assertEquals(expected, actual);

Преимущества этого в сравнении вручную по полям - это то, что вы сравниваете все свои поля, поэтому, даже если позже вы добавите новое поле в свой класс, оно будет автоматически проверено, по сравнению с тем, что вы использовали кучу assertEquals() для всех полей, которые затем должны быть обновлены, если вы добавите больше полей в свой класс.

jUnit также отображает строки для вас, чтобы вы могли прямо видеть, где они отличаются. Не уверенный, насколько надежным является упорядочение полей Gson, это может быть потенциальной проблемой.

Ответ 16

Я перепробовал все ответы, и у меня ничего не получалось.

Итак, я создал свой собственный метод, который сравнивает простые объекты Java, не углубляясь во вложенные структуры...

Метод возвращает ноль, если все поля совпадают или строка содержит детали несоответствия.

Только свойства, которые имеют метод получения, сравниваются.

Как пользоваться

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

Пример вывода, если есть несоответствие

Вывод показывает имена свойств и соответствующие значения сравниваемых объектов

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

Код (Java 8 и выше):

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

Ответ 17

Можете ли вы поместить код сравнения, который вы опубликовали, в какой-то статический метод утилиты?

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

Чем вы можете использовать этот метод в JUnit следующим образом:

assertEquals ( "Объекты не равны", "", findDifferences (obj1, obj));

который не является неуклюжим и дает вам полную информацию о различиях, если они существуют (не совсем в нормальной форме assertEqual, но вы получаете всю информацию, чтобы она была хорошей).

Ответ 18

Это не поможет OP, но это может помочь любым разработчикам С#, которые здесь заканчиваются...

Как Энрике опубликовал, вы должны переопределить метод equals.

Есть ли лучшая практика для получения того, что я хочу от объекта, без подкласса и переопределения равных (ugh)?

Мое предложение состоит в том, чтобы не использовать подкласс. Используйте частичный класс.

Определения неполных классов (MSDN)

Итак, ваш класс будет выглядеть как...

public partial class TheClass
{
    public override bool Equals(Object obj)
    {
        // your implementation here
    }
}

Для Java я согласен с предложением использовать рефлексию. Просто помните, что вам следует избегать использования отражения, когда это возможно. Это медленно, трудно отлаживать и еще труднее поддерживать в будущем, потому что IDE могут сломать ваш код, выполнив переименование поля или что-то в этом роде. Будьте осторожны!

Ответ 19

Из ваших комментариев к другим ответам я не понимаю, чего вы хотите.

Как раз для обсуждения, скажем, что класс переопределил метод equals.

Итак, ваш UT будет выглядеть примерно так:

SomeType expected = // bla
SomeType actual = // bli

Assert.assertEquals(expected, actual). 

И все готово. Более того, вы не можете получить "полную картину равенства", если утверждение не выполнено.

Из того, что я понимаю, вы говорите, что даже если тип сделал переопределение равным, вас это не интересовало бы, так как вы хотите получить "картину полного равенства". Таким образом, нет никакого смысла в распространении и переопределении равных.

Итак, у вас есть варианты: либо сравнить свойство по свойству, используя рефлексию или жестко запрограммированные проверки, я бы предложил последний. Или: сравните читаемые человеком представления этих объектов.

Например, вы можете создать вспомогательный класс, который сериализует тип, который вы хотите сопоставить с XML-документом, и сравните полученный XML! в этом случае вы можете визуально увидеть, что именно равно, а что нет.

Этот подход даст вам возможность взглянуть на полную картину, но она также относительно громоздка (и небольшая ошибка склонна сначала).

Ответ 20

Вы можете переопределить метод equals класса, например:

@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;
    }
}

Ну, если вы хотите сравнить два объекта с классом, вы должны каким-то образом реализовать методы equals и hash code