Автоматические модульные тесты для поддержки контрактов с объектными методами в Java? - PullRequest
10 голосов
/ 10 октября 2008

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

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

Ответы [ 8 ]

2 голосов
/ 10 октября 2008
    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 при обнаружении ошибки.

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

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

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

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

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

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

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

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

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

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


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

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

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

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

Новый ответ на старый вопрос, но в мае 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();
0 голосов
/ 19 октября 2008

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

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

См. Весь код по адресу: Автоматический тест для функции равенства в java-классах с помощью аннотаций

0 голосов
/ 10 октября 2008

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

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

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

0 голосов
/ 10 октября 2008

[публикация в сообществе здесь, без кармы;)]

Вот еще один код-вызов для вас:

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

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

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

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

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

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

Этот класс нужно скопировать-вставить в соответствующий пакет пустого Java-проекта ... и просто запустить;)


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

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

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

0 голосов
/ 10 октября 2008

Эта проблема не имеет «легкого» решения, если вы не накладываете строгие ограничения на свои классы.

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

0 голосов
/ 10 октября 2008

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

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

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

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