Как один модуль должен проверить контракт hashCode-equals? - PullRequest
72 голосов
/ 09 октября 2008

Вкратце, контракт hashCode, в соответствии с object.hashCode () Java:

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

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

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

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

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

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

Ответы [ 8 ]

69 голосов
/ 11 сентября 2009

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

11 голосов
/ 09 октября 2008

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

Например, допустим, у вас был пользовательский класс 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 голосов
/ 10 ноября 2008

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

4 голосов
/ 09 октября 2008

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

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

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

Повторюсь, цель моего вопроса - найти стандартные случаи тестов, чтобы подтвердить, что 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(), по моему мнению, правильный результат.

Ответ Эли Кортрайта - пример тщательного осмысления способа взлома алгоритма хеширования, основанного на знании спецификации equals. Пример специальной коллекции - хороший пример, так как пользовательские Collection иногда появляются, и они довольно склонны к взлому в алгоритме хеширования.

1 голос
/ 21 июля 2013

Вы также можете использовать что-то похожее на http://code.google.com/p/guava-libraries/source/browse/guava-testlib/src/com/google/common/testing/EqualsTester.java проверить равно и hashCode.

1 голос
/ 09 октября 2008

Это один из немногих случаев, когда в тесте было бы несколько утверждений. Так как вам нужно протестировать метод 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.

0 голосов
/ 22 июля 2013

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...