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


111

소스가없는 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)을 재정의하지 않고 객체에서 원하는 것을 얻는 더 나은 방법이 있습니까?


당신을 위해 깊은 비교를하는 라이브러리를 찾고 있습니까? stackoverflow.com/questions/1449001/… 에서 제안 된 deep-equals처럼 ?
Vikdor

3
두 인스턴스가 같지 않은 이유를 알아야하는 이유는 무엇입니까? 일반적으로 equal메서드 구현은 두 인스턴스가 같은지 여부 만 알려주며, 인텐스가 같지 않은 이유는 신경 쓰지 않습니다.
Bhesh Gurung

3
나는 그들을 고칠 수 있도록 어떤 속성이 같지 않은지 알고 싶습니다. :)
Ryan Nelson

모든 Objectequals메서드 가 있으며 재정의 된 equals 메서드가 없음을 의미했을 것입니다.
Steve Kuo

내가 생각할 수있는 가장 좋은 방법은 래퍼 클래스 나 하위 클래스를 사용한 다음 equals 메서드를 재정의 한 후 사용하는 것입니다.
Thihara

답변:


66

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

최신 버전의 Mockito 사용 :

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

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

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

17
이 클래스는 패키지에 org.mockito.internal.matchers.apachecommons있습니다. Mockito 문서 상태 : org.mockito.internal-> "내부 클래스, 클라이언트가 사용하지 않습니다." 이것을 사용하여 프로젝트를 위험에 빠뜨릴 것입니다. 이것은 모든 Mockito 버전에서 변경 될 수 있습니다. 여기에서 읽기 : site.mockito.org/mockito/docs/current/overview-summary.html
luboskrnac

6
Mockito.refEq()대신 사용하십시오 .
Jeremy Kao

1
Mockito.refEq()개체에 id 세트가 없을 때 실패합니다 = (
cavpollo

1
@PiotrAleksanderChmielowski, 죄송합니다. Spring + JPA + Entities로 작업 할 때 Entity 객체에는 ID (데이터베이스 테이블의 ID 필드를 나타냄)가있을 수 있으므로 비어 있으면 (아직 DB에 저장되지 않은 새 객체) refEq실패합니다. 해시 코드 메소드가 객체를 비교할 수 없기 때문에 비교할 수 있습니다.
cavpollo

3
잘 작동하지만 예상과 실제 순서가 잘못되었습니다. 반대 방향이어야합니다.
pkawiak

48

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

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

public class TestClass {

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

업데이트 : assertj v3.13.2에서이 메서드는 Woodz가 주석에서 지적한 것처럼 더 이상 사용되지 않습니다. 현재 권장 사항은

public class TestClass {

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

}

3
assertj v3.13.2에서이 방법은 더 이상 사용되지 않으며 권장 사용에 지금 usingRecursiveComparison()isEqualTo()라인이되도록,assertThat(actualObject).usingRecursiveComparison().isEqualTo(expectedObject);
Woodz

45

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

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

1
Gradle을 : androidTestCompile 'org.apache.commons : 평민 - lang3 : 3.5'
로엘

1
"org.apache.commons.lang3.builder.EqualsBuilder"를 사용하려는 경우 "dependencies"아래의 gradle 파일에 추가해야합니다
Roel

3
이것은 정확히 어떤 필드가 실제로 일치하지 않았는 지에 대한 힌트를 제공하지 않습니다.
Vadzim

1
@Vadzim 아래 코드를 사용하여 Assert.assertEquals (ReflectionToStringBuilder.toString (expected), ReflectionToStringBuilder.toString (actual));
Abhijeet Kushe

2
이것은 그래프의 모든 노드가 "equal"및 "hashcode"를 구현해야하므로 기본적으로이 방법을 거의 쓸모 없게 만듭니다. AssertJ의 isEqualToComparingFieldByFieldRecursively는 제 경우에 완벽하게 작동하는 것입니다.
John Zhang

12

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

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

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

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

이것이 사용입니다.

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

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


1
불행히도 Unitils죽은 것 같습니다 . stackoverflow.com/questions/34658067/is-unitils-project-alive를 참조하십시오 .
Ken Williams

8

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

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

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

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


1
이 접근 방식의 추가 이점은 예상 및 실제 값을 신경 쓰지 않는 필드를 제외한 문자열로 표시하는 시각적 출력을 얻는 것입니다.
fquinner

7

어설 션 (assertThat)에 hamcrest를 사용하고 있고 추가 테스트 라이브러리를 가져 오지 않으려면을 사용 SamePropertyValuesAs.samePropertyValuesAs하여 재정의 된 equals 메서드가없는 항목을 어설 션 할 수 있습니다 .

장점은 또 다른 테스트 프레임 워크를 가져올 필요가 없으며 . 같은 것을 사용하는 expected: field=<value> but was field=<something else>대신 assert가 실패 할 때 ( ) 유용한 오류를 제공 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).


1
작업을 위해 SamePropertyValuesAs 프로젝트 의존성 hamcrest.org/JavaHamcrest/…에
bigspawn

나는이 솔루션처럼, 그것은 "완전히 * 다른 추가 종속성을 추가하지 않는 한!
GhostCat


2

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

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


1

필드별로 비교 :

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

1
초기 어설 션 실패는 가능한 실패를 아래에 숨길 수 있기 때문에 이것은 내가하고 싶지 않은 일입니다.
Ryan Nelson

2
죄송합니다. 게시물의 해당 부분을 놓쳤습니다 ... 단위 테스트 환경에서 "완전한 평등 그림"이 중요한 이유는 무엇입니까? 필드가 모두 같거나 (테스트 통과) 모두 같지 않습니다 (테스트 실패).
EthanB 2012-08-27

다른 필드가 같지 않은지 확인하기 위해 테스트를 다시 실행하고 싶지 않습니다. 한 번에 해결할 수 있도록 불균등 한 모든 필드를 미리 알고 싶습니다.
Ryan Nelson

1
하나의 테스트에서 많은 필드를 주장하는 것은 진정한 '단위'테스트로 간주되지 않습니다. 전통적인 TDD (Test-Driven Development)를 사용하면 작은 테스트를 작성한 다음 통과 할 수있는 충분한 코드 만 작성합니다. 필드 당 하나의 어설 션을 갖는 것이이를 수행하는 올바른 방법입니다. 모든 어설 션을 하나의 테스트에 넣지 마십시오. 관심있는 각 필드 어설 션에 대해 다른 테스트를 만듭니다. 이렇게하면 스위트를 한 번 실행하여 모든 필드의 모든 오류를 볼 수 있습니다. 이것이 어렵다면 코드가 처음에 충분히 모듈화되지 않았으며 더 깨끗한 솔루션으로 리팩토링 될 수 있음을 의미합니다.
Jesse Webb

이것은 확실히 유효한 제안이며 단일 테스트에서 여러 단언을 해결하는 일반적인 방법입니다. 여기서 유일한 도전은 물체에 대한 전체적인 관점을 원한다는 것입니다. 즉, 개체가 유효한 상태인지 확인하기 위해 모든 필드를 동시에 테스트하고 싶습니다. 재정의 된 equals () 메서드 (물론이 예제에는 포함되지 않음)가있을 때 수행하기 어렵지 않습니다.
라이언 넬슨

1

AssertJ 어설 션은 #equals메소드를 적절하게 재정의 하지 않고 값을 비교하는 데 사용할 수 있습니다 . 예 :

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

// ...

assertThat(actual)
    .usingRecursiveComparison()
    .isEqualTo(expected);

1

이 질문은 오래되었으므로 JUnit 5를 사용하는 또 다른 현대적인 접근 방식을 제안 할 것입니다.

초기 주장이 실패하면 완전한 평등 그림을 얻지 못하기 때문에이 솔루션이 마음에 들지 않습니다.

JUnit 5에는 Assertions.assertAll()테스트의 모든 어설 션을 함께 그룹화 할 수 있는 메서드가 호출 되어 각 어설 션을 실행하고 마지막에 실패한 어설 션을 출력합니다. 즉, 먼저 실패한 어설 션은 후자의 어설 션 실행을 중지하지 않습니다.

assertAll("Test obj1 with obj2 equality",
    () -> assertEquals(obj1.getFieldA(), obj2.getFieldA()),
    () -> assertEquals(obj1.getFieldB(), obj2.getFieldB()),
    () -> assertEquals(obj1.getFieldC(), obj2.getFieldC()));

0

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


0

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

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

나는 객체가 다른 객체와 동일한 속성 값을 한 것으로 테스트에 비교 싶었지만, 같은 방법 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이면 일부 속성이 다릅니다.



0

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

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

assertEquals(expected, actual);

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

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


1
필드 순서는 Gson에 의해 보장되지 않습니다. 당신은 문자열 JsonParse에 원하고 JsonElements 구문 분석의 결과 비교 수
OZMA

0

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

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

모든 필드가 일치하거나 불일치 세부 정보가 포함 된 문자열 인 경우 메서드는 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);
    }

0

junit 전용의 대안으로 equals assertion 전에 필드를 null로 설정할 수 있습니다.

    actual.setCreatedDate(null); // excludes date assertion
    expected.setCreatedDate(null);

    assertEquals(expected, actual);

-1

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

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


-1

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

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

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

내 제안은 하위 클래스를 사용하지 않는 것입니다. 부분 클래스를 사용하십시오.

부분 클래스 정의 (MSDN)

그래서 수업은 ...

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

Java의 경우 리플렉션을 사용하라는 제안에 동의합니다. 가능하면 리플렉션을 사용하지 않는 것이 좋습니다. IDE가 필드 이름 변경 또는 이와 유사한 작업을 수행하여 코드를 손상시킬 수 있기 때문에 속도가 느리고 디버깅하기가 어렵고 향후 유지 관리가 더 어렵습니다. 조심해!


-1

귀하의 의견에서 다른 답변에 이르기까지 귀하가 원하는 것을 이해하지 못합니다.

토론을 위해 클래스가 equals 메서드를 재정의했다고 가정 해 보겠습니다.

따라서 UT는 다음과 같습니다.

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

Assert.assertEquals(expected, actual). 

그리고 당신은 끝났습니다. 또한 주장이 실패하면 "완전한 평등 그림"을 얻을 수 없습니다.

내가 이해하는 바에 따르면, 유형이 같음을 재정의하더라도 "완전한 평등 그림"을 얻고 싶기 때문에 그것에 관심이 없을 것입니다. 따라서 동등을 확장하고 재정의하는 것도 의미가 없습니다.

따라서 옵션이 필요합니다. 속성별로 속성을 비교하거나, 반사 또는 하드 코딩 된 검사를 사용하여 후자를 제안합니다. 또는 : 이러한 개체의 사람이 읽을 수있는 표현 을 비교 합니다.

예를 들어, XML 문서와 비교하려는 유형을 직렬화하고 결과 XML을 비교하는 도우미 클래스를 만들 수 있습니다! 이 경우 정확히 무엇이 같고 무엇이 아닌지 시각적으로 볼 수 있습니다.

이 접근 방식은 전체 그림을 볼 수있는 기회를 제공하지만 상대적으로 번거롭고 처음에는 약간의 오류가 발생하기 쉽습니다.


내 용어 "완전 평등 그림"이 혼란 스러울 수 있습니다. equals ()를 구현하면 실제로 문제가 해결됩니다. 테스트를 다시 실행할 필요없이 모든 불평등 필드 (평등과 관련된)를 동시에 아는 데 관심이 있습니다. 객체를 직렬화하는 것도 또 다른 가능성이지만 반드시 딥 같음이 필요하지는 않습니다. 가능한 경우 속성의 equals () 구현을 활용하고 싶습니다.
Ryan Nelson

큰! 질문에서 언급했듯이 속성의 동등성을 절대적으로 활용할 수 있습니다. 이 경우 가장 간단한 해결책 인 것 같지만 언급했듯이 코드는 매우 복잡 할 수 있습니다.
Vitaliy

-3

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

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


3
하지만 영업 이익은 그가 등호를 오버라이드 (override)하지 않는 것을 말한다, 그는 더 나은 방법을 원한다
Ankur
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.