한 단위가 hashCode-equals 계약을 어떻게 테스트해야합니까?


79

간단히 말해서, 자바의 object.hashCode ()에 따른 hashCode 계약 :

  1. equals ()에 영향을 미치는 것이 변경되지 않는 한 해시 코드는 변경되지 않아야합니다.
  2. equals ()는 해시 코드가 ==임을 의미합니다.

주로 불변 데이터 객체에 관심이 있다고 가정 해 봅시다. 생성 된 정보는 절대 변경되지 않으므로 # 1이 유지되는 것으로 간주됩니다. 그것은 # 2를 남깁니다. 문제는 단순히 해시 코드 ==를 의미한다는 것을 확인하는 것입니다.

분명히, 우리는 그 집합이 사소하게 작지 않는 한 모든 가능한 데이터 객체를 테스트 할 수 없습니다. 그렇다면 일반적인 경우를 포착 할 수있는 단위 테스트를 작성하는 가장 좋은 방법은 무엇입니까?

이 클래스의 인스턴스는 불변이므로 이러한 객체를 생성하는 방법은 제한되어 있습니다. 이 단위 테스트는 가능하면 모든 것을 다루어야합니다. 내 머리 꼭대기에서 진입 점은 생성자, 역 직렬화 및 하위 클래스의 생성자입니다 (생성자 호출 문제를 줄일 수 있어야 함).

[연구를 통해 내 질문에 답하려고 노력할 것입니다. 다른 StackOverflowers의 입력은이 프로세스에 대한 환영받는 안전 메커니즘입니다.]

[다른 OO 언어에도 적용 할 수 있으므로 해당 태그를 추가하겠습니다.]


1
일반적으로 같음 또는 해시 코드 구현으로 인해 계약이 이미 파기되었지만 둘다는 아닙니다. openpojo는 Java 프로젝트에 도움이됩니다. EqualsAndHashCodeMatchRule이 도움이됩니다. 이미 존재하는 답변은 나머지 계약 테스트에 대한 충분한 세부 정보를 제공합니다.
Michiel Leegwater

답변:


73

EqualsVerifier 는 비교적 새로운 오픈 소스 프로젝트이며 equals 계약을 테스트하는 데 매우 효과적입니다. GSBase의 EqualsTester 에는 문제 가 없습니다 . 나는 그것을 확실히 추천 할 것입니다.


7
EqualsVerifier를 사용하는 것만으로도 Java에 대해 배울 수 있습니다!
David

내가 미쳤어? EqualsVerifier는 값 객체 의미론을 전혀 확인하지 않는 것 같습니다! 내 클래스가 equals ()를 올바르게 구현한다고 생각하지만 내 클래스는 기본 "=="동작을 사용합니다. 어떻게 이럴 수있어?! 나는 단순한 것을 놓치고 있어야한다.
JB Rainsberger 2015

아하! equals ()를 재정의하지 않으면 EqualsVerifier는 모든 것이 정상이라고 가정합니다!? 그것은 내가 기대하는 것이 아닙니다. equals ()를 재정의하지 않는 것과 동일한 동작이 equals ()를 재정 의하여 super.equals ()와 똑같은 일을 할 것으로 기대합니다. 기묘한.
JB Rainsberger 2015

7
@JBRainsberger 안녕하세요! EqualsVerifier의 작성자는 여기에 있습니다. 혼란스러워서 죄송합니다. Re : equals를 재정의하지 않음 : "allFieldsShouldBeUsed ()"로 예상되는 동작을 활성화 할 수 있습니다. 이것은 당신이 언급 한 이유로 다음 버전에서 기본값이 될 것입니다. Re : 동일한 사례 : 예제 코드를 보지 않고는 도움을 줄 수 없다고 생각합니다. 새로운 질문이나 groups.google.com/forum/?fromgroups#!forum/equalsverifier 의 EV 메일 링리스트에 자세히 설명해 주 시겠습니까? 감사!
jqno 2015

1
EqualsVerifier는 정말 강력합니다. 같지 않은 객체의 잠재적 인 조합을 각각 테스트하지 않음으로써 엄청난 시간을 얻었습니다. 오류 메시지는 정말 정확하고 유익합니다. 코딩 규칙이 기본 예상과 다른 경우 검증 도구를 매우 구성 할 수 있습니다. 위대한 작업 @jqno
gontard

11

내 조언은 이것이 사실이 아닌 이유 / 방법을 생각한 다음 이러한 상황을 대상으로하는 단위 테스트를 작성하는 것입니다.

예를 들어 사용자 지정 Set클래스 가 있다고 가정 해 보겠습니다 . 두 세트가 동일한 요소를 포함하는 경우 동일하지만 두 세트의 기본 데이터 구조는 해당 요소가 다른 순서로 저장되는 경우 다를 수 있습니다. 예를 들면 :

MySet s1 = new MySet( new String[]{"Hello", "World"} );
MySet s2 = new MySet( new String[]{"World", "Hello"} );
assertEquals(s1, s2);
assertTrue( s1.hashCode()==s2.hashCode() );

이 경우 구현 한 해싱 알고리즘에 따라 세트의 요소 순서가 해시에 영향을 미칠 수 있습니다. 그래서 이것은 제가 작성한 테스트의 종류입니다. 왜냐하면 그것은 제가 동일하다고 정의한 두 개체에 대해 일부 해싱 알고리즘이 다른 결과를 생성 할 수 있다는 것을 알고있는 경우를 테스트하기 때문입니다.

어떤 것이 든 자신의 사용자 정의 클래스와 유사한 표준을 사용해야합니다.


6

이를 위해 junit 애드온을 사용할 가치가 있습니다. EqualsHashCodeTestCase http://junit-addons.sourceforge.net/ 클래스를 확인하십시오. 이것을 확장하고 createInstance 및 createNotEqualInstance를 구현할 수 있습니다. 그러면 equals 및 hashCode 메서드가 올바른지 확인합니다.


4

GSBase 의 EqualsTester 를 추천합니다 . 기본적으로 원하는 것을 수행합니다. 그래도 두 가지 (사소한) 문제가 있습니다.

  • 생성자는 모든 작업을 수행하므로 좋은 습관이라고 생각하지 않습니다.
  • 클래스 A의 인스턴스가 클래스 A의 하위 클래스의 인스턴스와 같으면 실패합니다. 이것이 반드시 같음 계약을 위반하는 것은 아닙니다.

2

[이 글을 쓰는 시점에 3 개의 다른 답변이 게시되었습니다.]

다시 말하지만, 내 질문의 목적은 확인 시험의 표준 경우 찾을 수 있습니다 hashCodeequals서로 동의하는 것입니다. 이 질문에 대한 나의 접근 방식은 문제의 클래스, 즉 불변 데이터를 작성할 때 프로그래머가 취하는 일반적인 경로를 상상하는 것입니다. 예를 들면 :

  1. equals()작성하지 않고 hashCode(). 이것은 종종 두 인스턴스의 필드가 동등 함을 의미하는 동등성이 정의되었음을 의미합니다.
  2. hashCode()작성하지 않고 equals(). 이것은 프로그래머가보다 효율적인 해싱 알고리즘을 찾고 있음을 의미 할 수 있습니다.

# 2의 경우 문제가없는 것 같습니다. 추가 인스턴스가 만들어 equals()지지 않았으므로 동일한 해시 코드를 갖는 데 추가 인스턴스가 필요하지 않습니다. 최악의 경우 해시 알고리즘은이 질문의 범위를 벗어난 해시 맵의 성능을 저하시킬 수 있습니다.

# 1의 경우 표준 단위 테스트는 생성자에 전달 된 동일한 데이터를 사용하여 동일한 개체의 두 인스턴스를 만들고 동일한 해시 코드를 확인하는 작업을 수반합니다. 오탐은 어떻습니까? 그럼에도 불구하고 건전하지 않은 알고리즘에서 동일한 해시 코드를 생성하는 생성자 매개 변수를 선택할 수 있습니다. 이러한 매개 변수를 피하는 경향이있는 단위 테스트는이 질문의 정신을 충족시킬 것입니다. 여기서 지름길은 소스 코드를 검사하고 equals(), 열심히 생각하고,이를 기반으로 테스트를 작성하는 것이지만, 경우에 따라 필요할 수 있지만 일반적인 문제를 포착하는 일반적인 테스트도있을 수 있으며 이러한 테스트도 정신을 충족시킵니다. 이 질문의.

클래스 테스트 할 경우 예를 들어, (데이터를) 호출하는 문자열로 구성된 문자열 소요 생성자 및 인스턴스가 equals()있었다 인스턴스를 굴복 equals()좋은 테스트는 것 아마 테스트 후를 :

  • new Data("foo")
  • 다른 new Data("foo")

내 생각에 올바른 결과를 산출하는 new Data(new String("foo"))것보다 올바른 해시 코드를 산출 할 가능성이 더 높지만 문자열이 인턴되지 않도록하기 위해 해시 코드를 확인할 수도 있습니다 Data.equals().

Eli Courtwright의 대답은 equals사양 에 대한 지식을 기반으로 해시 알고리즘을 깨는 방법을 열심히 생각하는 예입니다 . 특수 컬렉션의 예는 사용자가 만든 Collections가 때때로 나타나고 해시 알고리즘에서 엉망이되는 경향 이 있으므로 좋은 컬렉션입니다 .


1

이것은 테스트에서 여러 개의 어설 션이있는 유일한 경우 중 하나입니다. equals 메소드를 테스트해야하므로 동시에 hashCode 메소드도 확인해야합니다. 따라서 각 equals 메소드 테스트 케이스에서 hashCode 계약도 확인하십시오.

A one = new A(...);
A two = new A(...);
assertEquals("These should be equal", one, two);
int oneCode = one.hashCode();
assertEquals("HashCodes should be equal", oneCode, two.hashCode());
assertEquals("HashCode should not change", oneCode, one.hashCode());

물론 좋은 hashCode를 확인하는 것은 또 다른 연습입니다. 솔직히 저는 hashCode가 동일한 실행에서 변경되지 않았는지 확인하기 위해 이중 검사를 수행하지 않을 것입니다. 이러한 종류의 문제는 코드 검토에서 파악하여 개발자가 왜 이것이 좋은 방법이 아닌지 이해하도록 도와줌으로써 더 잘 처리됩니다. hashCode 메서드를 작성합니다.



0

내가 class를 가지고 있다면 Thing, 대부분의 다른 사람들처럼 ThingTest그 클래스에 대한 모든 단위 테스트를 보유 하는 class를 작성합니다 . 각각 ThingTest방법이 있습니다

 public static void checkInvariants(final Thing thing) {
    ...
 }

경우 Thing클래스 재정의 해시 코드가 동일하고는 방법이있다

 public static void checkInvariants(final Thing thing1, Thing thing2) {
    ObjectTest.checkInvariants(thing1, thing2);
    ... invariants that are specific to Thing
 }

이 메서드는 개체 쌍 사이에 유지되도록 설계된 모든 불변 을 확인 Thing합니다. ObjectTest객체의 쌍 사이에 유지해야하는 모든 불변을 확인하기위한 책임에 방법을 위임합니다. 마찬가지로 equals그리고 hashCode모든 객체 방법, 그 방법 확인되었는지 hashCodeequals 일치한다.

그런 다음 Thing개체 쌍을 생성 하고이를 pairwise checkInvariants메서드에 전달하는 몇 가지 테스트 메서드가 있습니다 . 등가 분할을 사용하여 테스트 할 가치가있는 쌍을 결정합니다. 일반적으로 각 쌍을 하나의 속성에서만 다르게 만들고 두 개의 동등한 객체를 테스트하는 테스트를 만듭니다.

나는 또한 때때로 3 개의 인수 checkInvariants방법을 가지고 있지만, 그것이 findinf 결함에서 덜 유용하다는 것을 알게 되었기 때문에 이것을 자주하지 않습니다.


이것은 다른 포스터가 여기서 언급 한 것과 유사합니다. stackoverflow.com/a/190989/545127
Raedwald
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.