web-dev-qa-db-ja.com

1つのユニットでhashCode-equalsコントラクトをどのようにテストする必要がありますか?

一言で言えば、Javaのobject.hashCode()によるhashCodeコントラクト:

  1. Equals()に影響する何かが変更されない限り、ハッシュコードは変更されません。
  2. equals()は、ハッシュコードが==であることを意味します

主に不変のデータオブジェクトに関心があると仮定しましょう-それらの情報は構築された後は決して変わらないため、#1が保持されると仮定されます。 #2を残します:問題は、単に等しいとハッシュコード==が含まれることを確認することの1つです。

明らかに、そのセットが非常に小さい場合を除き、考えられるすべてのデータオブジェクトをテストすることはできません。それで、一般的なケースをキャッチする可能性が高いユニットテストを書くための最良の方法は何ですか?

このクラスのインスタンスは不変であるため、そのようなオブジェクトを作成する方法は限られています。可能であれば、この単体テストですべてをカバーする必要があります。私の頭上では、エントリポイントは、コンストラクター、逆シリアル化、およびサブクラスのコンストラクターです(コンストラクター呼び出しの問題を軽減できるはずです)。

[研究を通じて自分の質問に答えようとします。他のStackOverflowersからの入力は、このプロセスへの歓迎すべき安全メカニズムです。]

[これは他のOO言語に適用される可能性があるため、このタグを追加しています。]

72
Paul Brinkley

EqualsVerifier は比較的新しいオープンソースプロジェクトであり、equals契約のテストで非常に優れた仕事をします。 issues GSBaseのEqualsTesterにはありません。絶対お勧めします。

69
Benno Richters

私のアドバイスは、なぜ/どのようにこれが当てはまらないのかを考え、それらの状況を対象としたユニットテストを書くことです。

たとえば、カスタムSetクラスがあるとします。同じ要素が含まれている場合、2つのセットは等しくなりますが、要素が異なる順序で格納されている場合、2つの等しいセットの基礎となるデータ構造が異なる可能性があります。例えば:

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

この場合、実装したハッシュアルゴリズムによっては、セット内の要素の順序がハッシュに影響する場合があります。したがって、これは、ある種のハッシュアルゴリズムが、等しいと定義した2つのオブジェクトに対して異なる結果を生成する可能性があることがわかっている場合をテストするため、作成するテストの一種です。

それが何であれ、独自のカスタムクラスで同様の標準を使用する必要があります。

11
Eli Courtwright

これにはjunitアドオンを使用する価値があります。クラスEqualsHashCodeTestCaseをチェックしてください http://junit-addons.sourceforge.net/ これを拡張してcreateInstanceとcreateNotEqualInstanceを実装できます。これにより、equalsとhashCodeメソッドが正しいことがチェックされます。

6
amc

GSBaseの EqualsTester をお勧めします。基本的にはあなたが望むことをします。ただし、2つの(マイナーな)問題があります。

  • コンストラクターはすべての作業を行いますが、これは良い習慣とは思いません。
  • クラスAのインスタンスがクラスAのサブクラスのインスタンスと等しい場合、失敗します。これは、必ずしも等号の違反ではありません。
4
Benno Richters

[この記事の執筆時点で、他に3つの回答が投稿されました。]

繰り返しになりますが、私の質問の目的は、hashCodeequalsが互いに一致することを確認するためのテストの標準的なケースを見つけることです。この質問に対する私のアプローチは、問題のクラス、つまり不変データを書くときにプログラマーがとる共通のパスを想像することです。例えば:

  1. equals()を書いていないhashCode()これは、多くの場合、2つのインスタンスのフィールドの等価性を意味するように等価性が定義されたことを意味します。
  2. hashCode()を書かずにequals()を書いた。これは、プログラマがより効率的なハッシュアルゴリズムを求めていたことを意味する可能性がある。

#2の場合、問題は私には存在しないようです。追加のインスタンスはequals()にされていないため、同じハッシュコードを持つために追加のインスタンスは必要ありません。最悪の場合、ハッシュアルゴリズムはハッシュマップのパフォーマンスを低下させる可能性がありますが、これはこの質問の範囲外です。

#1の場合、標準の単体テストでは、コンストラクターに渡される同じデータを使用して同じオブジェクトの2つのインスタンスを作成し、等しいハッシュコードを検証する必要があります。誤検知はどうですか?それにもかかわらず不健全なアルゴリズムで等しいハッシュコードを生成するコンストラクタパラメータを選択することは可能です。このようなパラメータを回避する傾向がある単体テストは、この質問の精神を満たすでしょう。ここでのショートカットはequals()のソースコードを調べ、よく考え、それに基づいてテストを書くことですが、これは場合によっては必要になるかもしれませんが、一般的な問題をキャッチする一般的なテストもあります-また、このようなテストはこの質問の精神を満たします。

たとえば、テスト対象のクラス(データと呼ばれる)に文字列を受け取るコンストラクタがあり、equals()である文字列から構築されたインスタンスがequals()であるインスタンスを生成した場合、良いテストはおそらくテストするでしょう:

  • new Data("foo")
  • 別のnew Data("foo")

new Data(new String("foo"))のハッシュコードをチェックして、Stringを強制収容しないようにすることもできますが、Data.equals()は正しい結果を生成するよりも正しいハッシュコードを生成する可能性が高くなります。私の考えでは。

Eli Courtwrightの答えは、equals仕様の知識に基づいてハッシュアルゴリズムを破る方法を真剣に考えた例です。ユーザーが作成したCollectionsは時々現れるので、特別なコレクションの例は良いものです。また、ハッシュアルゴリズムでかなり混乱する傾向があります。

2
Paul Brinkley

http://code.google.com/p/guava-libraries/source/browse/guava-testlib/src/com/google/common/testing/EqualsTester.Java のようなものを使用することもできます。 = equalsとhashCodeをテストします。

1
Mangoose

これは、テストで複数のアサートを行う唯一のケースの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メソッドを記述します。

1
Rob Spieldenner

クラスThingがある場合、他のほとんどの人がそうであるように、クラスThingTestを記述します。このクラスには、そのクラスのすべての単体テストが含まれています。各ThingTestにはメソッドがあります

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

ThingクラスがhashCodeをオーバーライドし、等しい場合はメソッドを持ちます

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

このメソッドは、Thingオブジェクトの任意のペア間で保持するように設計されたall不変条件をチェックする役割を果たします。委任先のObjectTestメソッドは、オブジェクトのペア間で保持する必要があるすべての不変条件をチェックします。 equalshashCodeはすべてのオブジェクトのメソッドであるため、そのメソッドはhashCodeequalsの整合性をチェックします。

その後、Thingオブジェクトのペアを作成し、それらをペアワイズcheckInvariantsメソッドに渡すテストメソッドがいくつかあります。等価分割を使用して、テストする価値のあるペアを決定します。通常、1つの属性のみが異なるように各ペアを作成し、さらに2つの同等のオブジェクトをテストするテストを作成します。

私は時々3つの引数checkInvariantsメソッドも持っていますが、findinf欠陥ではあまり有用ではないことがわかりますので、これを頻繁に行いません

0
Raedwald