clone()
大規模なプロジェクト内でさまざまな操作の単体テストを作成しようとしています。同じタイプの2つのオブジェクトを取得し、詳細に比較できる既存のクラスがどこかにあるかどうか疑問に思っています。同一かどうか?
clone()
大規模なプロジェクト内でさまざまな操作の単体テストを作成しようとしています。同じタイプの2つのオブジェクトを取得し、詳細に比較できる既存のクラスがどこかにあるかどうか疑問に思っています。同一かどうか?
回答:
Unitilsには次の機能があります。
リフレクションによる等価アサーション。Javaのデフォルト/ null値の無視やコレクションの順序の無視など、さまざまなオプションがあります。
この質問が大好きです!主にそれがほとんど答えられないか、または悪い答えをされたからです。まだ誰もそれを理解していないようです。バージンテリトリー:)
まず、の使用については考えないでくださいequals
。の規約はequals
、javadocで定義されているように、等価関係(再帰的、対称的、推移的)であり、等価関係ではありません。そのためには、反対称である必要もあります。これの唯一の実装はequals
、真の等価関係です(またはそうである可能性があります)java.lang.Object
。equals
グラフのすべてを比較するために使用したとしても、契約を破るリスクは非常に高くなります。Josh BlochがEffective Javaで指摘したように、イコールの契約は破るのが非常に簡単です:
「インスタンス化可能なクラスを拡張して、equalsコントラクトを維持しながらアスペクトを追加する方法はありません。」
とにかく、ブール方式は本当に何が良いのですか?オリジナルとクローンのすべての違いを実際にカプセル化するのは良いことだと思いませんか?また、ここでは、グラフ内の各オブジェクトの比較コードの記述/維持に煩わされるのではなく、時間とともに変化するソースに合わせてスケーリングするものを探していると仮定します。
Soooo、本当に欲しいのはある種の状態比較ツールです。このツールの実装方法は、ドメインモデルの性質とパフォーマンスの制限に実際に依存しています。私の経験では、一般的な魔法の弾丸はありません。そして、それは多数の反復にわたって遅くなります。しかし、クローン操作の完全性をテストするためには、かなりうまくいきます。最適な2つのオプションは、シリアル化とリフレクションです。
発生するいくつかの問題:
XStreamはかなり高速で、XMLUnitと組み合わせると数行のコードで機能します。XMLUnitは、すべての違いを報告したり、最初に見つかった箇所で停止したりできるので便利です。そしてその出力には、異なるノードへのxpathが含まれています。デフォルトでは、順序付けられていないコレクションは許可されませんが、そうするように構成できます。特別な差分ハンドラー(aと呼ばDifferenceListener
れます)を注入すると、順序の無視など、差分を処理する方法を指定できます。ただし、最も単純なカスタマイズを超えて何かを実行するとすぐに、作成が難しくなり、詳細が特定のドメインオブジェクトに結び付けられる傾向があります。
私の個人的な好みは、リフレクションを使用して、宣言されたすべてのフィールドを循環し、各フィールドにドリルダウンして、差分を追跡することです。警告の言葉:スタックオーバーフロー例外が好きでない限り、再帰を使用しないでください。スタックを使用して対象物を保持します(LinkedList
か何か)。私は通常、一時フィールドと静的フィールドを無視し、すでに比較したオブジェクトのペアをスキップするので、誰かが自己参照コードを書くことに決めた場合に無限ループに陥ることはありません(ただし、常にプリミティブラッパーを比較します、同じオブジェクト参照がしばしば再利用されるため)。コレクションの順序を無視したり、特殊なタイプやフィールドを無視したりするように前もって設定できますが、アノテーションを使用してフィールド自体の状態比較ポリシーを定義したいと思います。これがIMHOであり、クラスのメタデータを実行時に利用できるようにするために、アノテーションが意図したものです。何かのようなもの:
@StatePolicy(unordered=true, ignore=false, exactTypesOnly=true)
private List<StringyThing> _mylist;
これは本当に難しい問題だと思いますが、完全に解決できます!そして、あなたのために機能するものがあれば、それは本当に、本当に、便利です:)
とても幸運。そして、あなたが純粋な天才である何かを思いついた場合は、共有することを忘れないでください!
java-util内のDeepEqualsおよびDeepHashCode()を参照してください:https : //github.com/jdereg/java-util
このクラスは、元の作者が要求したとおりのことを行います。
public
、コレクション/マップにのみ適用できるのではなく、すべてのタイプに適用する必要があるものだけを比較することを主張します。
Hibernate Enversによって改訂された2つのエンティティインスタンスの比較を実装する必要がありました。私は独自の違いを書き始めましたが、次のフレームワークを見つけました。
https://github.com/SQiShER/java-object-diff
同じタイプの2つのオブジェクトを比較すると、変更、追加、削除が表示されます。変更がない場合、オブジェクトは(理論的には)等しくなります。チェック中に無視する必要があるゲッター用のアノテーションが用意されています。フレームワークには、同等性チェックよりもはるかに広いアプリケーションがあります。つまり、変更ログの生成に使用しています。
そのパフォーマンスは問題ありません。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);
おそらくそれはすべてのケースでうまくいくわけではありませんが、あなたが考えるより多くのケースでうまくいくでしょう。
ドキュメントの内容は次のとおりです。
プロパティ/フィールドごとの再帰的なプロパティ/フィールドの比較(継承されたものを含む)に基づいて、テスト中のオブジェクト(実際)が指定されたオブジェクトと等しいことをアサートします。これは、actualのequalsの実装が適切でない場合に役立ちます。再帰的なプロパティ/フィールド比較は、カスタムのequals実装を持つフィールドには適用されません。つまり、フィールドごとの比較ではなく、オーバーライドされたequalsメソッドが使用されます。
再帰的比較はサイクルを処理します。デフォルトでは、floatは1.0E-6の精度で比較され、doubleは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にはマッチャーsamePropertyValuesAsがあります。ただし、JavaBeans規約に依存しています(ゲッターとセッターを使用)。比較されるオブジェクトに属性のゲッターとセッターがない場合、これは機能しません。
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
}
}
ユーザーBean-ゲッターとセッター
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);
}
}
リンクリストの例は、それほど難しくありません。コードが2つのオブジェクトグラフをトラバースするときに、訪問したオブジェクトをセットまたはマップに配置します。別のオブジェクト参照にトラバースする前に、このセットは、オブジェクトがすでにトラバースされているかどうかを確認するためにテストされます。その場合、さらに進む必要はありません。
LinkedListを使用すると述べた上記の人に同意します(Stackのようですが、それに同期されたメソッドがないため、より高速です)。スタックを使用してオブジェクトグラフをトラバースし、リフレクションを使用して各フィールドを取得することは、理想的なソリューションです。一度書かれると、この「外部」equals()および「外部」hashCode()は、すべてのequals()およびhashCode()メソッドが呼び出す必要があるものです。二度と顧客のequals()メソッドは必要ありません。
私は、完全なオブジェクトグラフを横断するコードを少し書いて、Google Codeにリストしました。json-io(http://code.google.com/p/json-io/)を参照してください。JavaオブジェクトグラフをJSONにシリアル化し、そこから逆シリアル化します。パブリックコンストラクターの有無にかかわらず、SerializeableまたはNot SerializableなどのすべてのJavaオブジェクトを処理します。この同じトラバーサルコードは、外部の「equals()」および外部の「hashcode()」実装の基礎になります。ところで、JsonReader / JsonWriter(json-io)は通常、組み込みのObjectInputStream / ObjectOutputStreamよりも高速です。
このJsonReader / JsonWriterは比較に使用できますが、ハッシュコードには役立ちません。ユニバーサルhashcode()とequals()が必要な場合は、独自のコードが必要です。一般的なグラフのビジターでこれをうまくやることができるかもしれません。わかります。
その他の考慮事項-静的フィールド-簡単です-静的フィールドはすべてのインスタンス間で共有されるため、すべてのequals()インスタンスが静的フィールドに対して同じ値を持つため、それらをスキップできます。
一時的なフィールドについては-それは選択可能なオプションになります。時には、トランジェントに他の時間をカウントさせたくない場合があります。「あなたは時々ナッツのように感じますが、時にはそうではありません。」
json-ioプロジェクト(他のプロジェクトの場合)に戻って確認すると、外部のequals()/ hashcode()プロジェクトが見つかります。名前はまだありませんが、一目瞭然です。
Apacheは何かを提供し、両方のオブジェクトを文字列に変換して文字列を比較しますが、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ソリューションに触発された最も簡単なソリューションだと思いますは、オブジェクトをシリアル化してから、生の結果を深く比較する。
シリアライゼーションは、バイト、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());
}
}
Java 7以降Objects.deepEquals(Object, Object)
。