Як одна одиниця повинна перевірити контракт hashCode-дорівнює?


79

У двох словах, контракт hashCode, відповідно до об'єкта Java.hashCode ():

  1. Хеш-код не повинен змінюватися, якщо не зміниться щось, що впливає на equals ()
  2. equals () передбачає, що хеш-коди = =

Давайте припустимо, що інтерес насамперед стосується незмінних об’єктів даних - їх інформація ніколи не змінюється після їх побудови, тому передбачається, що №1 має місце. Залишається # 2: проблема полягає лише в тому, щоб підтвердити, що дорівнює, означає хеш-код ==.

Очевидно, що ми не можемо перевірити кожен мислимий об’єкт даних, якщо цей набір не є тривіально малим. Отже, який найкращий спосіб написати модульний тест, який, ймовірно, охоплює типові випадки?

Оскільки екземпляри цього класу незмінні, існує обмежений спосіб побудови такого об'єкта; цей одиничний тест повинен охоплювати їх усіх, якщо це можливо. Зверху моєї голови точками входу є конструктори, десеріалізація та конструктори підкласів (які повинні бути зведені до проблеми виклику конструктора).

[Я спробую відповісти на власне запитання за допомогою дослідження. Вхідні дані від StackOverflowers - це вітальний механізм безпеки для цього процесу.]

[Це може бути застосовним до інших мов OO, тому я додаю цей тег.]


1
Зазвичай я виявляю, що контракт вже був розірваний через впровадження рівного або хеш-коду, але не обох. Там openpojo допомагає у проекті Java. У цьому допомагає EqualsAndHashCodeMatchRule. Вже наявні відповіді містять достатньо деталей щодо тестування решти контракту.
Michiel Leegwater

Відповіді:


73

EqualsVerifier - відносно новий проект з відкритим кодом, і він дуже добре справляється з тестуванням контракту equals. У нього немає проблем, які має EqualsTester від GSBase. Я б точно рекомендував це.


7
Просто використання EqualsVerifier навчило мене чомусь про Java!
Девід

Я божевільна? Здається, EqualsVerifier не перевіряв семантику Value Object! Він вважає, що мій клас правильно реалізує equals (), але мій клас використовує поведінку "==" за замовчуванням. Як це може бути ?! Напевно, мені не вистачає чогось простого.
JB Rainsberger

Ага! Якщо я не перевизначую equals (), тоді EqualsVerifier припускає, що все в порядку !? Це не те, що я очікував. Я очікував би такої самої поведінки для не перевизначення equals (), як для перевизначення equals () робити те саме те саме super.equals (). Дивно.
JB Rainsberger

7
@JBRainsberger Привіт! Творець програми EqualsVerifier тут. Мені шкода, що ви вважаєте це заплутаним. Re: не перевизначення дорівнює: ви можете включити очікувану поведінку за допомогою "allFieldsShouldBeUsed ()". Це буде типовим варіантом у наступній версії із зазначених вами причин. Re: однакові екземпляри: Я не думаю, що можу допомогти вам, не побачивши приклад коду. Ви можете пояснити нове запитання або список розсилки електромобілів за адресою groups.google.com/forum/?fromgroups#!forum/equalsverifier ? Дякую!
jqno

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

Я б порекомендував EqualsTester від GSBase. Це робить в основному те, що ви хочете. У мене з цим є дві (незначні) проблеми:

  • Конструктор виконує всю роботу, що я не вважаю доброю практикою.
  • Це не вдається, коли екземпляр класу А дорівнює екземпляру підкласу класу А. Це не обов'язково є порушенням контракту рівності.

2

[На момент написання цієї статті було розміщено ще три відповіді.]

Повторюючи, мета мого запитання - знайти стандартні випадки тестів, що підтверджують це hashCodeта equalsузгоджуються між собою. Я підходжу до цього питання, щоб уявити загальні шляхи, якими рухаються програмісти під час написання відповідних класів, а саме незмінні дані. Наприклад:

  1. Писав, equals()не пишучи hashCode(). Це часто означає, що рівність визначалася як рівність полів двох екземплярів.
  2. Писав, hashCode()не пишучи equals(). Це може означати, що програміст шукав більш ефективний алгоритм хешування.

У випадку №2 мені здається, що проблема не існує. Жодних додаткових екземплярів не було зроблено equals(), тому додаткові екземпляри не повинні мати однакових хеш-кодів. У гіршому випадку хеш-алгоритм може призвести до гіршої продуктивності хеш-карт, що виходить за рамки цього питання.

У випадку №1, стандартний модульний тест передбачає створення двох екземплярів одного і того ж об'єкта з однаковими даними, що передаються конструктору, та перевірку рівних хеш-кодів. А як щодо помилкових спрацьовувань Можна вибрати параметри конструктора, які випадково дають рівні хеш-коди на тим не менш необгрунтованому алгоритмі. Одиничний тест, який прагне уникати таких параметрів, відповідав би духу цього питання. Ярлик тут полягає в тому, щоб перевірити вихідний код equals(), добре подумати і написати тест на основі цього, але, хоча це може знадобитися в деяких випадках, можуть бути і загальні тести, які ловлять загальні проблеми - і такі тести також виконують дух цього питання.

Наприклад, якщо у класі, що перевіряється (назвемо його Data), є конструктор, який приймає String, і екземпляри, побудовані з рядків, які отримують equals()екземпляри, які були equals(), то хороший тест, мабуть, перевірить:

  • new Data("foo")
  • інший new Data("foo")

Ми могли б навіть перевірити хеш-код new Data(new String("foo")), щоб змусити рядок не інтернувати, хоча це, швидше за все, дасть правильний хеш-код, ніж Data.equals()дасть правильний результат, на мій погляд.

Відповідь Елі Кортрайт - це приклад продумування способу розбити хеш-алгоритм на основі знання equalsспецифікації. Приклад спеціальної колекції є хорошим, оскільки користувальницькі Collections іноді з’являються, і вони досить схильні до обробки алгоритму хешування.


1

Це один з єдиних випадків, коли в тесті я мав би кілька тверджень. Оскільки вам потрібно протестувати метод equals, вам слід одночасно перевірити і метод hashCode. Отже, на кожному з ваших рівних методів тестування також перевіряйте контракт 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

Якщо у мене є клас 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об'єктів. ObjectTestМетод він делегує відповідає за перевірку всіх інваріантів , які повинні утримувати між будь-якою парою об'єктів. Як equalsі hashCodeє методами всіх об'єктів, цей метод перевіряє це hashCodeі equalsузгоджується.

Потім у мене є кілька методів тестування, які створюють пари Thingоб’єктів і передають їх парному checkInvariantsметоду. Я використовую розділення еквівалентності, щоб вирішити, які пари варто перевірити. Я зазвичай створюю кожну пару, щоб бути різними лише в одному атрибуті, плюс тест, який тестує два еквівалентні об'єкти.

У мене також іноді є checkInvariantsметод з 3 аргументами , хоча я вважаю, що це менш корисно для виявлення дефектів, тому я не часто це роблю


Це схоже на те , що інший плакат згадує тут: stackoverflow.com/a/190989/545127
Raedwald
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.