Модульные тесты Automagic для поддержки контрактов Object method в Java?

При разработке приложений Java я часто переопределяю методы Object (обычно равные и hashCode). Я хотел бы каким-то образом систематически проверить, что я придерживаюсь контракта для методов Object для каждого из моих классов. Например, я хочу, чтобы тесты утверждали, что для равных объектов хэш-код также равен. Я использую тестовую среду JUnit, поэтому желательно, чтобы мне было какое-то решение JUnit, где я могу автоматически генерировать эти тесты, или какой-либо тестовый пример, который может каким-то образом посетить все мои классы и убедиться, что контракт поддерживается.

Я использую JDK6 и JUnit 4.4.

Ответ 1

    public static void checkObjectIdentity(Object a1, Object a2, Object b1) {
        assertEquals(a1, a2);
        assertEquals(a2, a1);
        assertNotSame(a1, a2);
        assertEquals(a1.hashCode(), a2.hashCode());
        assertFalse(a1.equals(b1));
        assertFalse(a2.equals(b1));
        assertFalse(b1.equals(a1));
        assertFalse(b1.equals(a2));
    }

Использование:

        checkObjectIdentity(new Integer(3), new Integer(3), new Integer(4));

Не могу придумать ничего лучшего. Добавьте новые вызовы checkObjectIdentity, когда найдете ошибку.

Ответ 2

Просто некоторые первоначальные мысли по этому вопросу (что может объяснить, почему до сих пор нет ответа после полного часа!?;)

Кажется, есть две части, когда дело доходит до решения вопроса:

1/вернуть все классы. Легко, вы даете имя jar, метод инициализации теста Junit:

  • проверьте, что этот jar находится в пути класса выполнения JUnit
  • читать и загружать все классы в нем
  • запоминает только те, для которых equals() и hash() объявлены и переопределены (через Reflection)

2/проверить все объекты
... и в этом заключается catch: вы должны создавать экземпляры этих объектов, которые создают два экземпляра, и использовать их для тестов equals().

Это означает, что если ваши конструкторы принимают аргументы, вы должны учитывать,

  • для примитивных типов аргументов (int, boolean, float,...) или String, каждая комбинация предельных значений (для String, "xxx", "", null; fonr int, 0, -x, + x, -Integer.MIN, + Integer.MAX,... и т.д.)
  • для не-примитивных типов, создайте экземпляр тех, которые будут переданы конструктору тестируемого объекта (что означает, что вы рекурсивно должны учитывать параметры конструктора этого параметра: примитивные типы или нет)

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

Однако, похоже, это возможно (вы можете сделать это code-challenge, если хотите), но я хочу сначала разрешить другим Читатели StackOverflow отвечают на эту проблему, поскольку они могут видеть гораздо более простое решение, которое я есть.


Чтобы избежать проблемы с комбинациями и чтобы тестовые значения релевантных были близки к самому фактическому коду, я бы рекомендовал определение выделенной аннотации с строкой, представляющей допустимые значения для конструкторов. Он был бы расположен выше переопределенного метода equals() одного из ваших объектов.

Затем будут прочитаны эти значения аннотаций, и экземпляры, созданные из них, будут объединены для тестирования equals(). Это позволило бы количество комбинаций достаточно вниз

Side- node: общий тестовый пример JUnit, конечно же, проверит, что для каждого equals() для тестов есть:

  • некоторые аннотации, как описано выше (если только не доступен только конструктор по умолчанию)
  • соответствующий метод hash() также переопределен (если нет, если бы он выбрал исключение assert и отказал в этом классе)

Ответ 3

Я думаю, что VonC на правильном пути, но я бы даже согласился на что-то менее сложное, например, параметризованный тест, который принимает объект .class(для которого тестируются методы Object), за которым следует переменное число конструктор args. Затем вам нужно будет использовать отражение, чтобы найти конструктор, который соответствует типам аргументов переданного, и вызвать конструктор. Этот тест предполагает, что передаваемые в него параметры будут создавать действительный экземпляр объекта.

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

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

Ответ 4

У этой проблемы нет "легкого" решения, если вы не ставите сильные ограничения на свои классы.

Например, если вы используете несколько конструкторов для данного класса, как вы можете обеспечить, чтобы все ваши параметры были хорошо учтены в ваших методах equals/hash? Как насчет значений по умолчанию? Это вещи, которые, к сожалению, не могут быть автоматизированы вслепую.

Ответ 5

[сообщение сообщества здесь, никакая карма не участвует;)]

Вот еще code-challenge:

Один класс java, реализующий тестовый пример JUnit, с основным методом, позволяющим запускать JUnit самостоятельно!

Этот класс также будет:

  • переопределить hash() и equals()
  • определить несколько атрибутов (с примитивными типами)
  • определить конструктор по умолчанию, но также некоторые конструкторы с различными комбинациями параметров
  • определить аннотацию, способную перечислить "интересные" значения для перехода к тем конструкторам
  • аннотировать equals() с этими "интересными" значениями

Метод test принимает параметр имени класса (здесь: он будет сам), проверьте, имеет ли класс с этим именем метод overals с равенствами() с аннотациями "интересные значения".
Если да, то он построит соответствующие экземпляры (сам по себе) на основе аннотаций и проверит equals()

Это самодостаточный тестовый класс, который определяет механизм, который может быть обобщен для любого класса с аннотированной переопределенной функцией equals().

Пожалуйста, используйте JDK6 и JUnit4.4

Этот класс следует скопировать-вставить в соответствующий пакет пустого java-проекта... и просто запуститься;)


Чтобы добавить еще несколько соображений в ответ на Николаса (см. комментарии):

  • да, данные, необходимые для тестирования, находятся в кандидате на класс, который должен быть протестирован (т.е. один переопределяет равные значения и помогает любому "автоматическому тестеру" создавать соответствующие экземпляры).
  • Я не вижу точно так же, как "логика тестирования", но как полезные комментарии к тому, что должно делать равное (и, кстати, как данные, которые будут использоваться вышеупомянутым тестером;))

Должны ли аннотации, представляющие потенциальные данные тестирования, никогда не быть в самом классе?... Эй, это может быть отличный вопрос:)

Ответ 6

Возможно, я не понимаю вопрос (и слишком часто CS), но это не похоже, что проблема, которую вы описываете, разрешима в общем случае.

Другими словами, единственный способ, чтобы unit test мог заверить вас, что переопределяющий метод работает одинаково на всех входах, поскольку переопределенный метод должен был бы попробовать его на всех входах; в случае равных, это будет означать все состояния объекта.

Я не уверен, будет ли какая-либо текущая среда тестирования автоматически обрезать и абстрагировать возможности для вас.

Ответ 7

У меня есть первая грубая реализация, для тестирования равных с конструктором, используя здесь только примитивные параметры. Просто скопируйте его в файл test.MyClass.java и запустите его.

Предупреждение: 1720 строк кода (0 ошибок в findbugs, 0 в "измененном" контрольном стиле, цикломатическая сложность до 10 для всех функций).

Посмотрите весь код по адресу: Автотест для эквивалентной функции в классах Java через аннотации

Ответ 8

Новый ответ на старый вопрос, но в мае 2011 года Guava (ранее Google Collections) выпустил класс, который удаляет много шаблон, называемый EqualsTester. Вам все равно придется создавать свои собственные экземпляры, но он заботится о сравнении каждого объекта с самим собой, нулевому, каждому объекту в группе равенства, каждому объекту в каждой другой группе равенств и тайному экземпляру, который не должен ничего сопоставлять. Он также проверяет, что a.equals(b) подразумевает a.hashCode() == b.hashCode() для всех этих комбинаций.

Пример из Javadoc:

new EqualsTester()
   .addEqualityGroup("hello", "h" + "ello")
   .addEqualityGroup("world", "wor" + "ld")
   .addEqualityGroup(2, 1 + 1)
   .testEquals();