clone()
대규모 프로젝트 내 에서 다양한 작업에 대한 단위 테스트를 작성하려고하는데 같은 유형의 두 개체를 가져 와서 깊은 비교를 수행 할 수있는 기존 클래스가 어딘가에 있는지 궁금합니다. 똑같은가요?
clone()
대규모 프로젝트 내 에서 다양한 작업에 대한 단위 테스트를 작성하려고하는데 같은 유형의 두 개체를 가져 와서 깊은 비교를 수행 할 수있는 기존 클래스가 어딘가에 있는지 궁금합니다. 똑같은가요?
답변:
Unitils 에는 다음과 같은 기능이 있습니다.
Java 기본값 / 널 값 무시 및 컬렉션 순서 무시와 같은 다양한 옵션을 사용하여 리플렉션을 통한 평등 주장
나는이 질문을 좋아한다! 주로 거의 대답하지 않았거나 나쁘게 대답하지 않았기 때문입니다. 아직 아무도 알아 내지 못한 것 같습니다. 버진 영토 :)
우선,도없는 생각 사용에 대한 equals
. 의 계약 equals
javadoc 내에서 정의는 동치 관계 (재귀, 대칭 및 전이)입니다 하지 동등 관계. 이를 위해서는 비대칭이어야합니다. 그것의 유일한 구현은 equals
진정한 평등 관계입니다 java.lang.Object
. equals
그래프의 모든 것을 비교 하는 데 사용 했더라도 계약을 위반할 위험이 상당히 높습니다. Josh Bloch가 Effective Java 에서 지적했듯이 동등 계약은 매우 쉽게 깨뜨릴 수 있습니다.
"균등 계약을 유지하면서 인스턴스화 가능한 클래스를 확장하고 측면을 추가 할 수있는 방법은 없습니다."
어쨌든 부울 메서드가 실제로 무슨 소용이 있습니까? 원본과 복제품 사이의 모든 차이점을 실제로 캡슐화하는 것이 좋을 것 같지 않습니까? 또한 여기서는 그래프의 각 개체에 대한 비교 코드를 작성 / 유지하는 데 신경 쓰지 않고 시간이 지남에 따라 변경 될 때 소스에 따라 확장 될 무언가를 찾고 있다고 가정합니다.
정말 원하는 것은 일종의 상태 비교 도구입니다. 이 도구를 구현하는 방법은 실제로 도메인 모델의 특성과 성능 제한에 따라 다릅니다. 제 경험상 일반적인 마법 총알은 없습니다. 그리고 많은 반복에서 느려질 것 입니다. 그러나 복제 작업의 완전성을 테스트하기 위해서는 작업을 꽤 잘 수행 할 것입니다. 가장 좋은 두 가지 옵션은 직렬화와 반영입니다.
발생할 수있는 몇 가지 문제 :
XStream은 매우 빠르며 XMLUnit과 결합하면 몇 줄의 코드만으로 작업을 수행 할 수 있습니다. XMLUnit은 모든 차이점을보고하거나 처음 발견 한 부분에서 멈출 수 있기 때문에 좋습니다. 출력에는 다른 노드에 대한 xpath가 포함되어 있습니다. 기본적으로 정렬되지 않은 컬렉션을 허용하지 않지만 그렇게하도록 구성 할 수 있습니다. 특별한 차이 핸들러 (라고 함 DifferenceListener
)를 삽입 하면 순서 무시를 포함하여 차이를 처리하려는 방식을 지정할 수 있습니다. 그러나 가장 단순한 사용자 지정 이상의 작업을 수행하려는 즉시 작성하기가 어려워지고 세부 정보가 특정 도메인 개체에 묶여있는 경향이 있습니다.
개인적으로 선호하는 것은 리플렉션을 사용하여 선언 된 모든 필드를 순환하고 각 필드를 드릴 다운하여 이동하면서 차이점을 추적하는 것입니다. 경고 : 스택 오버플로 예외가 마음에 들지 않는 한 재귀를 사용하지 마십시오. 스택을 사용하여 범위 내에 유지 (사용LinkedList
또는 뭔가). 나는 일반적으로 과도 및 정적 필드를 무시하고 이미 비교 한 객체 쌍을 건너 뛰기 때문에 누군가가 자기 참조 코드를 작성하기로 결정한 경우 무한 루프로 끝나지 않습니다 (그러나 나는 항상 기본 래퍼를 비교합니다. , 동일한 객체 참조가 종종 재사용되기 때문에). 컬렉션 순서를 무시하고 특수 유형 또는 필드를 무시하도록 미리 구성 할 수 있지만 주석을 통해 필드 자체에 대한 상태 비교 정책을 정의하고 싶습니다. IMHO는 런타임에 클래스에 대한 메타 데이터를 제공하기 위해 주석이 의미하는 바입니다. 다음과 같은 것 :
@StatePolicy(unordered=true, ignore=false, exactTypesOnly=true)
private List<StringyThing> _mylist;
나는 이것이 실제로 정말 어려운 문제라고 생각하지만 완전히 해결할 수 있습니다! 그리고 일단 당신에게 효과가있는 것이 있으면 정말, 정말, 편리합니다 :)
그럼 행운을 빕니다. 그리고 순수한 천재적인 것을 생각해 내면 공유하는 것을 잊지 마십시오!
java-util : https://github.com/jdereg/java-util 내의 DeepEquals 및 DeepHashCode ()를 참조하십시오.
이 클래스는 원래 작성자가 요청한 작업을 정확히 수행합니다.
public
합니다.
Hibernate Envers에 의해 수정 된 두 엔티티 인스턴스의 비교를 구현하기 만하면됩니다. 나는 내 자신의 다른 글을 쓰기 시작했지만 다음 프레임 워크를 발견했습니다.
https://github.com/SQiShER/java-object-diff
동일한 유형의 두 개체를 비교할 수 있으며 변경, 추가 및 제거가 표시됩니다. 변경 사항이 없으면 객체가 동일합니다 (이론상). 검사 중에 무시해야하는 게터에 대한 주석이 제공됩니다. 프레임 작업은 동등성 검사보다 훨씬 더 광범위한 응용 프로그램을 가지고 있습니다. 즉, 변경 로그를 생성하는 데 사용하고 있습니다.
성능은 괜찮습니다. JPA 엔티티를 비교할 때 먼저 엔티티 관리자에서 분리해야합니다.
저는 XStream을 사용하고 있습니다.
/**
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object o) {
XStream xstream = new XStream();
String oxml = xstream.toXML(o);
String myxml = xstream.toXML(this);
return myxml.equals(oxml);
}
/**
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
XStream xstream = new XStream();
String myxml = xstream.toXML(this);
return myxml.hashCode();
}
에서 AssertJ , 당신은 할 수 있습니다 :
Assertions.assertThat(expectedObject).isEqualToComparingFieldByFieldRecursively(actualObject);
아마도 모든 경우에 작동하지는 않지만 생각할 수있는 더 많은 경우에 작동 할 것입니다.
문서 내용은 다음과 같습니다.
테스트중인 개체 (실제)가 속성 / 필드 비교 (상속 된 항목 포함)에 의한 속성 / 필드의 재귀 적 비교를 기반으로 주어진 개체와 동일하다는 것을 확인합니다. 실제와 같음 구현이 적합하지 않은 경우 유용 할 수 있습니다. 재귀 속성 / 필드 비교는 사용자 정의 같음 구현이있는 필드에 적용되지 않습니다. 즉, 필드 별 비교 대신 재정의 된 같음 메서드가 사용됩니다.
재귀 비교는주기를 처리합니다. 기본적으로 부동 소수점은 1.0E-6의 정밀도와 비교되고 1.0E-15의 두 배가됩니다.
각각 usingComparatorForFields (Comparator, String ...) 및 usingComparatorForType (Comparator, Class)을 사용하여 (중첩 된) 필드 또는 유형별로 사용자 지정 비교기를 지정할 수 있습니다.
비교할 개체는 다른 유형일 수 있지만 동일한 속성 / 필드를 가져야합니다. 예를 들어 실제 객체에 이름 문자열 필드가있는 경우 다른 객체에도 하나가 있어야합니다. 개체에 이름이 같은 필드와 속성이있는 경우 속성 값이 필드 위에 사용됩니다.
isEqualToComparingFieldByFieldRecursively
이제 더 이상 사용되지 않습니다. 사용 assertThat(expectedObject).usingRecursiveComparison().isEqualTo(actualObject);
: 대신
http://www.unitils.org/tutorial-reflectionassert.html
public class User {
private long id;
private String first;
private String last;
public User(long id, String first, String last) {
this.id = id;
this.first = first;
this.last = last;
}
}
User user1 = new User(1, "John", "Doe");
User user2 = new User(1, "John", "Doe");
assertReflectionEquals(user1, user2);
Hamcrest에는 matcher samePropertyValuesAs가 있습니다. 그러나 JavaBeans Convention (getter 및 setter 사용)에 의존합니다. 비교할 객체에 속성에 대한 getter 및 setter가 없으면 작동하지 않습니다.
import static org.hamcrest.beans.SamePropertyValuesAs.samePropertyValuesAs;
import static org.junit.Assert.assertThat;
import org.junit.Test;
public class UserTest {
@Test
public void asfd() {
User user1 = new User(1, "John", "Doe");
User user2 = new User(1, "John", "Doe");
assertThat(user1, samePropertyValuesAs(user2)); // all good
user2 = new User(1, "John", "Do");
assertThat(user1, samePropertyValuesAs(user2)); // will fail
}
}
사용자 빈-게터 및 세터 포함
public class User {
private long id;
private String first;
private String last;
public User(long id, String first, String last) {
this.id = id;
this.first = first;
this.last = last;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getFirst() {
return first;
}
public void setFirst(String first) {
this.first = first;
}
public String getLast() {
return last;
}
public void setLast(String last) {
this.last = last;
}
}
isFoo
대한 읽기 메소드를 사용하는 POJO를 가질 때까지 훌륭하게 작동 Boolean
합니다. 그것을 고치기 위해 2016 년부터 열린 PR이 있습니다. github.com/hamcrest/JavaHamcrest/pull/136
객체가 Serializable을 구현하는 경우 다음을 사용할 수 있습니다.
public static boolean deepCompare(Object o1, Object o2) {
try {
ByteArrayOutputStream baos1 = new ByteArrayOutputStream();
ObjectOutputStream oos1 = new ObjectOutputStream(baos1);
oos1.writeObject(o1);
oos1.close();
ByteArrayOutputStream baos2 = new ByteArrayOutputStream();
ObjectOutputStream oos2 = new ObjectOutputStream(baos2);
oos2.writeObject(o2);
oos2.close();
return Arrays.equals(baos1.toByteArray(), baos2.toByteArray());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
Linked List 예제는 처리하기 어렵지 않습니다. 코드가 두 개체 그래프를 순회 할 때 방문한 개체를 Set 또는 Map에 배치합니다. 다른 개체 참조로 이동하기 전에이 집합은 개체가 이미 이동되었는지 확인하기 위해 테스트됩니다. 그렇다면 더 이상 갈 필요가 없습니다.
나는 LinkedList를 사용한다고 말한 사람에 동의합니다 (스택과 같지만 동기화 된 메서드가 없으므로 더 빠릅니다). 반사를 사용하여 각 필드를 가져 오는 동안 스택을 사용하여 개체 그래프를 탐색하는 것이 이상적인 솔루션입니다. 한 번 작성하면이 "외부"equals () 및 "external"hashCode ()는 모든 equals () 및 hashCode () 메소드가 호출해야하는 것입니다. 다시는 고객 equals () 메소드가 필요하지 않습니다.
Google 코드에 나열된 전체 개체 그래프를 가로 지르는 약간의 코드를 작성했습니다. json-io (http://code.google.com/p/json-io/)를 참조하세요. Java 개체 그래프를 JSON으로 직렬화하고 그로부터 역 직렬화합니다. 공용 생성자, 직렬화 가능 여부, 직렬화 불가능 등 모든 Java 객체를 처리합니다.이 동일한 순회 코드는 외부 "equals ()"및 외부 "hashcode ()"구현의 기반이됩니다. Btw, JsonReader / JsonWriter (json-io)는 일반적으로 기본 제공 ObjectInputStream / ObjectOutputStream보다 빠릅니다.
이 JsonReader / JsonWriter는 비교에 사용할 수 있지만 해시 코드에는 도움이되지 않습니다. 범용 hashcode () 및 equals ()를 원하면 자체 코드가 필요합니다. 일반적인 그래프 방문자와 함께이 문제를 해결할 수 있습니다. 우리는 볼 것이다.
기타 고려 사항-정적 필드-간단합니다. 정적 필드가 모든 인스턴스에서 공유되므로 모든 equals () 인스턴스가 정적 필드에 대해 동일한 값을 가지므로 건너 뛸 수 있습니다.
임시 필드의 경우 선택 가능한 옵션입니다. 때로는 과도 상태가 다른 시간을 계산하지 않기를 원할 수 있습니다. "때로는 너트처럼 느껴지지만 때로는 그렇지 않습니다."
json-io 프로젝트 (다른 프로젝트의 경우)로 다시 확인하면 외부 equals () / hashcode () 프로젝트를 찾을 수 있습니다. 아직 이름은 없지만 분명 할 것입니다.
아파치는 당신에게 무언가를 제공하고 두 객체를 문자열로 변환하고 문자열을 비교하지만 toString ()을 재정의해야합니다.
obj1.toString().equals(obj2.toString())
toString () 재정의
모든 필드가 기본 유형 인 경우 :
import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
@Override
public String toString() {return
ReflectionToStringBuilder.toString(this);}
기본이 아닌 필드 및 / 또는 컬렉션 및 / 또는 맵이있는 경우 :
// Within class
import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
@Override
public String toString() {return
ReflectionToStringBuilder.toString(this,new
MultipleRecursiveToStringStyle());}
// New class extended from Apache ToStringStyle
import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import java.util.*;
public class MultipleRecursiveToStringStyle extends ToStringStyle {
private static final int INFINITE_DEPTH = -1;
private int maxDepth;
private int depth;
public MultipleRecursiveToStringStyle() {
this(INFINITE_DEPTH);
}
public MultipleRecursiveToStringStyle(int maxDepth) {
setUseShortClassName(true);
setUseIdentityHashCode(false);
this.maxDepth = maxDepth;
}
@Override
protected void appendDetail(StringBuffer buffer, String fieldName, Object value) {
if (value.getClass().getName().startsWith("java.lang.")
|| (maxDepth != INFINITE_DEPTH && depth >= maxDepth)) {
buffer.append(value);
} else {
depth++;
buffer.append(ReflectionToStringBuilder.toString(value, this));
depth--;
}
}
@Override
protected void appendDetail(StringBuffer buffer, String fieldName,
Collection<?> coll) {
for(Object value: coll){
if (value.getClass().getName().startsWith("java.lang.")
|| (maxDepth != INFINITE_DEPTH && depth >= maxDepth)) {
buffer.append(value);
} else {
depth++;
buffer.append(ReflectionToStringBuilder.toString(value, this));
depth--;
}
}
}
@Override
protected void appendDetail(StringBuffer buffer, String fieldName, Map<?, ?> map) {
for(Map.Entry<?,?> kvEntry: map.entrySet()){
Object value = kvEntry.getKey();
if (value.getClass().getName().startsWith("java.lang.")
|| (maxDepth != INFINITE_DEPTH && depth >= maxDepth)) {
buffer.append(value);
} else {
depth++;
buffer.append(ReflectionToStringBuilder.toString(value, this));
depth--;
}
value = kvEntry.getValue();
if (value.getClass().getName().startsWith("java.lang.")
|| (maxDepth != INFINITE_DEPTH && depth >= maxDepth)) {
buffer.append(value);
} else {
depth++;
buffer.append(ReflectionToStringBuilder.toString(value, this));
depth--;
}
}
}}
그러한 깊은 비교를위한 중단 보장이 문제가 될 수 있습니다. 다음은 무엇을해야합니까? (이러한 비교기를 구현하면 좋은 단위 테스트가 될 것입니다.)
LinkedListNode a = new LinkedListNode();
a.next = a;
LinkedListNode b = new LinkedListNode();
b.next = b;
System.out.println(DeepCompare(a, b));
또 다른 것이 있습니다.
LinkedListNode c = new LinkedListNode();
LinkedListNode d = new LinkedListNode();
c.next = d;
d.next = c;
System.out.println(DeepCompare(c, d));
Using an answer instead of a comment to get a longer limit and better formatting.
이것이 주석이라면 왜 답변 섹션을 사용합니까? 그것이 내가 그것을 신고 한 이유입니다. 때문에 ?
. 이 답변은 댓글을 남기지 않은 다른 사람이 이미 신고했습니다. 방금 리뷰 대기열에 있습니다. 내가 더 조심 했어야했다는 것이 내 잘못 일 수 있습니다.
Ray Hulha 솔루션에서 영감을 얻은 가장 쉬운 솔루션 은 객체를 직렬화 한 다음 원시 결과를 자세히 비교하는 것입니다.
직렬화는 byte, json, xml 또는 간단한 toString 등이 될 수 있습니다. ToString이 더 저렴한 것 같습니다. Lombok은 우리를 위해 무료로 쉽게 사용자 정의 할 수있는 ToSTring을 생성합니다. 아래 예를 참조하십시오.
@ToString @Getter @Setter
class foo{
boolean foo1;
String foo2;
public boolean deepCompare(Object other) { //for cohesiveness
return other != null && this.toString().equals(other.toString());
}
}