equalsメソッドなしで2つのクラスの等価性をアサートするにはどうすればよいですか?


111

ソースがない、equals()メソッドのないクラスがあるとします。そのクラスの2つのインスタンスで同等であることを主張したいと思います。

複数のアサートを実行できます:

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の問題を説明していません)。3番目のオプションはコンパレータを使用することですが、compareTo()はどのフィールドが等価に失敗したかを教えてくれません。

等しい(サブ)をサブクラス化してオーバーライドせずに、オブジェクトから必要なものを取得するためのより良いプラクティスはありますか?


あなたはあなたのために深い比較をするライブラリを探していますか?ディープイコールで示唆したようにstackoverflow.com/questions/1449001/...
Vikdor 2012

3
2つのインスタンスが等しくなかった理由を知る必要があるのはなぜですか。通常、equalメソッドの実装は2つのインスタンスが等しいかどうかを通知するだけであり、インスタンスが等しくない理由は気にしません。
Bhesh Gurung 2012

3
どのプロパティが等しくないかを知りたいので、修正できます。:)
Ryan Nelson

すべてObjectのにはequalsメソッドがあり、オーバーライドされた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 '19年

1
@PiotrAleksanderChmielowski、申し訳ありませんが、Spring + JPA + Entitiesを使用する場合、EntityオブジェクトはID(データベーステーブルのIDフィールドを表す)を持つ可能性があるため、空の場合(DBにまだ格納されていない新しいオブジェクト)はrefEq失敗しますhashcodeメソッドはオブジェクトを比較できないため、比較します。
cavpollo

3
正常に動作しますが、期待と実際の順序が間違っています。それは逆でなければなりません。
pkawiak 2017

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:commons-lang3:3.5'
Roel

1
「org.apache.commons.lang3.builder.EqualsBuilder」を使用する場合は、これを「依存関係」の下のgradleファイルに追加する必要があります
Roel

3
これは、どのフィールドが実際に一致しなかったかについてのヒントを与えません。
Vadzim 2017年

1
@Vadzim Assert.assertEquals(ReflectionToStringBuilder.toString(expected)、ReflectionToStringBuilder.toString(actual));を取得するために以下のコードを使用しました。
Abhijeet Kushe 2017年

2
これは、グラフ内のすべてのノードに「等しい」と「ハッシュコード」を実装する必要があるため、基本的にこのメソッドはほとんど役に立たなくなります。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を参照してください。
ケンウィリアムズ

8

Apache commons lang ReflectionToStringBuilderを使用できます

テストする属性を1つずつ指定するか、不要な属性を除外することができます。

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

次に、通常どおり2つの文字列を比較します。反射が遅いという点については、これはテストのためだけのものであるため、それほど重要ではないはずです。


1
このアプローチの追加の利点は、関心のあるフィールドを除いて、期待値と実際の値を文字列として表示する視覚的な出力が得られることです。
fquinner 2017

7

アサート(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)。


1
作業用SamePropertyValuesAsプロジェクトdependecyに追加する必要があります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
これは、私がやりたくないことです。初期のアサートの失敗は、起こりうる失敗を以下に隠すからです。
ライアンネルソン

2
申し訳ありませんが、あなたの投稿の一部を逃しました...ユニットテスト環境で「完全な平等の状況」が重要なのはなぜですか?フィールドがすべて等しい(テストに合格する)か、またはすべてが等しくない(テストが失敗する)かのいずれかです。
EthanB 2012

他のフィールドが等しくないかどうかを確認するためにテストを再実行する必要はありません。等しくないすべてのフィールドを前もって知りたいので、一度にそれらに対処できます。
ライアンネルソン

1
1つのテストで多くのフィールドをアサートすることは、真の「単体」テストとは見なされません。従来のテスト駆動開発(TDD)では、小さなテストを記述し、それを通過させるのに十分なコードのみを記述します。フィールドごとに1つのアサートを設定するのが正しい方法です。すべてのアサートを1つのテストに入れないでください。気になるフィールドアサーションごとに異なるテストを作成します。これにより、スイートの1回の実行ですべてのフィールドのすべてのエラーを確認できます。これが難しい場合は、そもそもコードが十分にモジュール化されておらず、よりクリーンなソリューションにリファクタリングできる可能性があります。
Jesse Webb

これは間違いなく有効な提案であり、1つのテストで複数のアサートを解決する通常の方法です。ここでの唯一の課題は、オブジェクトの全体像を知りたいということです。つまり、すべてのフィールドを同時にテストして、オブジェクトが有効な状態であることを確認します。オーバーライドされた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

これは、フィールドの値について同じクラスの2つのオブジェクトを比較する一般的な比較メソッドです(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()オブジェクトのid属性にnull値があるなどの理由で機能しません。

だから、これは私が見つけた解決策でした(まあ、同僚が見つけました):

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()上にすべてのフィールド。クラスにフィールドを追加する場合は、更新する必要があります。

jUnitは文字列も表示するので、文字列の違いを直接確認できます。フィールドの順序付けがどれほど信頼できるかはわかりませんGsonが、それは潜在的な問題になる可能性があります。


1
フィールドの順序はGsonによって保証されていません。文字列をJsonParseして、解析の結果であるJsonElementsを比較することができます
ozma

0

私はすべての答えを試しましたが、実際には何もうまくいきませんでした。

ネストされた構造に深く入り込むことなく単純なJavaオブジェクトを比較する独自のメソッドを作成しました...

すべてのフィールドが一致するか、不一致の詳細を含む文字列の場合、メソッドはnullを返します。

ゲッターメソッドを持つプロパティのみが比較されます。

使い方

        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-onlyの代替として、equalsアサーションの前にフィールドを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#開発者に役立つ可能性があります...

同様エンリケ投稿、あなたはequalsメソッドをオーバーライドする必要があります。

等しい(サブ)をサブクラス化してオーバーライドせずに、オブジェクトから必要なものを取得するためのより良いプラクティスはありますか?

私の提案は、サブクラスを使用しないことです。部分クラスを使用します。

部分クラス定義(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). 

これで完了です。さらに、アサーションが失敗すると、「完全な平等の状況」を取得できません。

私が理解していることから、型が等号をオーバーライドしたとしても、「完全な等式の図」を取得したいので、その型には興味がないと言っています。したがって、equalsを拡張してオーバーライドすることにも意味がありません。

したがって、オプションを使用する必要があります。プロパティごとに比較するか、リフレクションチェックまたはハードコードチェックを使用するか、後者をお勧めします。または:これらのオブジェクトの人間が読める表現を比較します。

たとえば、XMLドキュメントと比較する型をシリアル化し、結果のXMLを比較するヘルパークラスを作成できます。この場合、正確に等しいものと等しくないものを視覚的に確認できます。

このアプローチでは、全体像を確認する機会が得られますが、比較的面倒です(最初は少しエラーが発生しやすくなります)。


私の「完全な平等の描写」という用語は紛らわしいかもしれません。equals()を実装すると、実際に問題が解決します。テストを再実行しなくても、すべての等しくないフィールド(等しいことに関連)を同時に知ることに興味があります。オブジェクトをシリアル化することも可能ですが、必ずしも深いイコールが必要なわけではありません。可能であれば、プロパティのequals()実装を利用したいと思います。
ライアンネルソン

すごい!あなたの質問で述べたように、あなたは絶対にプロパティの等しいものを利用するかもしれません。これはこの場合最も簡単な解決策のようですが、あなたが指摘したように、コードは非常に厄介なものになる可能性があります。
Vitaliy 2012

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

まあ、クラスの2つのオブジェクトを比較したい場合は、何らかの方法で等号とハッシュコードメソッドを実装する必要があります


3
しかしOPは、イコールをオーバーライドしたくない、より良い方法を望んでいると述べています
Ankur
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.