Nice programing

equals 메소드없이 두 클래스에서 동등성을 어떻게 주장합니까?

nicepro 2020. 10. 7. 08:19
반응형

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

이것은 나에게 완전한 평등 그림을 제공하지만 투박합니다 (그리고 가능한 null 문제에 대해서도 설명하지 않았습니다). 세 번째 옵션은 Comparator를 사용하는 것이지만 compareTo ()는 어떤 필드가 동일하지 않은지 알려주지 않습니다.

하위 클래스를 지정하고 같음 (ugh)을 재정의하지 않고 객체에서 원하는 것을 얻는 더 나은 방법이 있습니까?


Mockito 는 리플렉션 매칭 기를 제공합니다.

최신 버전의 Mockito 사용 :

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

이전 버전의 경우 다음을 사용하십시오.

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

저는 일반적으로 org.apache.commons.lang3.builder.EqualsBuilder를 사용하여이 사용 사례를 구현합니다.

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

여기에 많은 정답이 있지만 내 버전도 추가하고 싶습니다. 이것은 Assertj를 기반으로합니다.

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

public class TestClass {

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

조금 오래되었다는 것을 알고 있지만 도움이 되었으면합니다.

나는 당신과 똑같은 문제에 부딪 혔기 때문에 조사 후 이것과 비슷한 질문을 거의 발견하지 못했습니다. 그리고 해결책을 찾은 후에 나는 다른 사람들을 도울 수 있다고 생각했기 때문에 그 질문에 똑같이 대답했습니다.

유사한 질문에 대해 가장 많이 득표 한 답변 (저자가 선택한 답변이 아님)이 가장 적합한 솔루션입니다.

기본적으로 Unitils 라는 라이브러리를 사용하여 구성됩니다 .

이것이 사용입니다.

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

클래스 User가 구현하지 않아도 통과 합니다 equals(). 더 많은 예제와 assertLenientEquals그들의 튜토리얼 에서 호출되는 정말 멋진 어설 션을 볼 수 있습니다 .


Apache commons lang ReflectionToStringBuilder를 사용할 수 있습니다.

테스트 할 속성을 하나씩 지정하거나 원하지 않는 속성을 제외하는 것이 좋습니다.

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

그런 다음 두 문자열을 정상적으로 비교합니다. 반사가 느리다는 점에 대해서는 이것이 테스트 용이라고 가정하므로 그렇게 중요하지 않아야합니다.


어설 션 (assertThat)에 hamcrest를 사용하고 있고 추가 테스트 라이브러리를 가져 오지 않으려면을 사용 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) 또는 얕은 비교 (hamcrest).


라이브러리 Hamcrest 1.3 Utility Matchers 에는 같음 대신 반사를 사용하는 특수 매 처가 있습니다.

assertThat(obj1, reflectEquals(obj2));

Shazamcrest를 사용하면 다음을 수행 할 수 있습니다.

assertThat(obj1, sameBeanAs(obj2));

반사 비교 방법 중 일부는 얕습니다.

또 다른 옵션은 객체를 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))

필드별로 비교 :

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

리플렉션을 사용하여 완전 동등성 테스트를 "자동화"할 수 있습니다. 단일 필드에 대해 작성한 같음 "추적"코드를 구현 한 다음 리플렉션을 사용하여 개체의 모든 필드에서 해당 테스트를 실행할 수 있습니다.


이것은 필드의 값에 대해 동일한 클래스의 두 객체를 비교하는 일반적인 비교 메소드입니다 (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 ;
    }
}

나는 매우 유사한 사건을 우연히 발견했습니다.

나는 객체가 다른 객체와 동일한 속성 값을 한 것으로 테스트에 비교 싶었지만, 같은 방법 is(), refEq()등은에 NULL 값을 가진 내 개체 등의 이유로 작동하지 않을 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이면 일부 속성이 다릅니다.


AssertJ의 일반적인 경우에 사용자 지정 비교기 전략을 만들 수 있습니다.

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

어설 션에서 사용자 지정 비교 전략 사용

AssertJ 예제


Android 앱을 단위 테스트 할 때 똑같은 수수께끼가 있었고 가장 쉬운 해결책은 Gson사용 하여 실제 값과 예상 값 개체를 json문자열 로 변환 하고 비교하는 것입니다.

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

assertEquals(expected, actual);

필드별로 수동으로 비교하는 것보다이 방법의 장점은 모든 필드 를 비교한다는 것입니다 . 따라서 나중에 클래스에 새 필드를 추가하더라도 on을 사용하는 경우에 비해 자동으로 테스트 assertEquals()됩니다. 모든 필드. 클래스에 필드를 더 추가하면 업데이트해야합니다.

jUnit은 또한 문자열을 표시하므로 서로 다른 부분을 직접 확인할 수 있습니다. 필드 순서가 얼마나 신뢰할 수 있는지 확실하지 않으면 Gson잠재적 인 문제가 될 수 있습니다.


나는 모든 대답을 시도했지만 실제로는 나를 위해 일한 것이 없습니다.

그래서 중첩 구조에 깊이 들어 가지 않고 간단한 자바 객체를 비교하는 나만의 방법을 만들었습니다.

모든 필드가 일치하거나 불일치 세부 정보가 포함 된 문자열이면 메서드가 null을 반환합니다.

getter 메서드가있는 속성 만 비교됩니다.

사용하는 방법

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

게시 한 비교 코드를 정적 유틸리티 메서드에 넣을 수 있습니까?

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의 정상적인 형태는 아니지만 모든 정보를 얻으므로 좋을 것입니다).


이것은 OP에 도움이되지 않지만, 여기까지 오는 C # 개발자에게 도움이 될 수 있습니다.

Enrique가 게시 한 것처럼 equals 메소드를 재정의해야합니다.

Is there a better practice to get what I want from the object, without subclassing and overridding equals (ugh)?

My suggestion is to not use a subclass. Use a partial class.

Partial Class Definitions (MSDN)

So your class would look like...

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

For Java, I would agree with the suggestion to use reflection. Just remember that you should avoid using reflection whenever possible. It is slow, hard to debug, and even harder to maintain into the future because IDEs could break your code by doing a field rename or something like that. Be careful!


From your comments to other answers, I don't understand what you want.

Just for the sake of discussion, lets say that the the class did override the equals method.

So your UT will look something like:

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

Assert.assertEquals(expected, actual). 

And you are done. Moreover, you can not get the "full equality picture" if the assertion fails.

From what I understand, you are saying that even if the type did override equals, you would not be interested in it, since you want to get the "full equality picture". So there is no point in extending and overriding equals either.

So you have to options: either compare property by property, using reflection or hard-coded checks, I would suggest the latter. Or: compare human readable representations of these objects.

For example, you can create a helper class that serializes the type you wish tocompare to an XML document and than compare the resulting XML! in this case, you can visually see what exactly is equal and what is not.

This approach will give you the opportunity to look at the full picture but it is also relatively cumbersome (and a little error prone at first).


다음과 같이 클래스의 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;
    }
}

글쎄요, 만약 당신이 클래스의 두 객체를 비교하고 싶다면 당신은 어떤 식 으로든 같음과 해시 코드 메소드를 구현해야합니다.

참고 URL : https://stackoverflow.com/questions/12147297/how-do-i-assert-equality-on-two-classes-without-an-equals-method

반응형