2008-10-09 2 views
62

В двух словах, договор Hashcode, согласно Object.hashCode Явы():Как один блок тестирует контракт hashCode-equals?

  1. хэш-код не должен измениться, если что-то влияет на равных() не изменяет
  2. равна() означает, хэш коды = =

Предположим, что мы заинтересованы прежде всего в неизменяемых объектах данных - их информация никогда не изменяется после их создания, поэтому предполагается, что # 1 удерживается. Это оставляет # 2: проблема заключается лишь в подтверждении того, что equals подразумевает хэш-код ==.

Очевидно, что мы не можем тестировать каждый мыслимый объект данных, если только этот набор тривиально мал. Итак, каков наилучший способ написать единичный тест, который, вероятно, поймает распространенные случаи?

Поскольку экземпляры этого класса являются неизменными, существуют ограниченные способы построения такого объекта; этот модульный тест должен охватывать все, если это возможно. В верхней части моей головы точки входа - это конструкторы, десериализация и конструкторы подклассов (которые должны быть приведены к проблеме вызова конструктора).

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

[Это может быть применимо и к другим языкам ОО, поэтому я добавляю этот тег.]

ответ

9

Мой совет будет думать о том, почему/как это может когда-либо быть недействительным, а затем написать некоторые модульные тесты, которые предназначены для этих ситуаций.

Например, предположим, что у вас есть собственный класс 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()); 

В этом случае порядок элементов в наборах может повлиять на их хэш, в зависимости от алгоритма хеширования вы реализованный. Так что это те тесты, которые я бы написал, так как он проверяет случай, когда я знаю, что для некоторого алгоритма хэширования можно будет произвести разные результаты для двух объектов, которые я определил как равные.

Вы должны использовать аналогичный стандарт со своим собственным пользовательским классом, что бы это ни было.

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.

4

Я бы порекомендовал EqualsTester от GSBase. Это в основном то, что вы хотите.У меня есть две (мелкие) проблемы:

  • Конструктор выполняет всю работу, которую я не считаю хорошей практикой.
  • Это терпит неудачу, если экземпляр класса A равен экземпляру подкласса класса A. Это не обязательно является нарушением равного контракта.
2

[На момент написания этой статьи, три другие ответы были опубликованы.]

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

  1. писал equals() без написания hashCode(). Это часто означает, что определение равенства означает равенство полей двух экземпляров.
  2. Написано hashCode() без письменного разрешения equals(). Это может означать, что программист искал более эффективный алгоритм хеширования.

В случае № 2 проблема кажется мне несущественной. Никаких дополнительных примеров не было сделано equals(), поэтому для получения одинаковых хэш-кодов дополнительных экземпляров не требуется. В худшем случае алгоритм хеширования может давать худшую производительность для хэш-карт, что выходит за рамки этого вопроса.

В случае № 1 стандартное тестирование устройства предусматривает создание двух экземпляров одного и того же объекта с теми же данными, переданными конструктору, и проверка равных хэш-кодов. Как насчет ложных срабатываний? Можно выбрать параметры конструктора, которые, как оказалось, дают одинаковые хеш-коды на тем не менее неудовлетворительном алгоритме. Единичный тест, который стремится избежать таких параметров, будет соответствовать духу этого вопроса. Ярлык здесь, чтобы проверить исходный код для equals(), подумайте, и напишите на нем тест, но в некоторых случаях это может быть необходимо, также могут быть общие тесты, которые улавливают общие проблемы - и такие тесты также выполняют дух этого вопроса.

Например, если класс для тестирования (назовем его Data) имеет конструктор, который принимает строку, и экземпляры построены из строк, которые equals() получены экземпляры, которые были equals(), то хороший тест, вероятно, тест:

  • new Data("foo")
  • другой new Data("foo")

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

Ответ Eli Courtwright - это пример того, как трудно преодолеть хэш-алгоритм, основанный на знании спецификации equals. Пример специальной коллекции - хороший, так как пользовательские Collection s иногда появляются и весьма склонны к muckups в алгоритме хеширования.

6

Для этого стоит использовать юнит-аддоны. Проверьте класс EqualsHashCodeTestCase http://junit-addons.sourceforge.net/, вы можете расширить его и реализовать createInstance и createNotEqualInstance, это проверит правильность методов equals и hashCode.

60

EqualsVerifier - относительно новый проект с открытым исходным кодом, и он очень хорошо работает при тестировании равного контракта. У него нет issues EqualsTester от GSBase. Я определенно рекомендовал бы это.

+7

Просто _using_ EqualsVerifier научил меня кое-что о Java! – David 2014-11-07 20:49:35

+0

Я сошел с ума? EqualsVerifier, похоже, не проверял семантику Object объекта вообще! Он считает, что мой класс правильно реализует equals(), но мой класс использует поведение по умолчанию «==». Как это может быть?! Я должен пропустить что-то простое. – 2015-11-03 13:27:49

0

Если у меня есть класс Thing, как и большинство других, я пишу класс ThingTest, который содержит все модульные тесты для этого класса. Каждый ThingTest имеет метод

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

и если Thing класса переопределяет хэш-код и равен он имеет метод

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. Я использую разделение эквивалентности, чтобы решить, какие пары стоит тестировать. Обычно я создаю каждую пару, чтобы отличаться только от одного атрибута, плюс тест, который проверяет два эквивалентных объекта.

я иногда есть метод 3 аргумент checkInvariants, хотя считает, что менее полезно в findinf дефектов, так что я не делаю это часто

Смежные вопросы