Я пишу модульные тесты для объектов, которые клонируются, сериализуются и / или записываются в файл XML.Во всех трех случаях я хотел бы убедиться, что результирующий объект «такой же», как и исходный.Я прошел несколько итераций в своем подходе и, обнаружив ошибки во всех из них, интересовался, что сделали другие люди.
Моя первая идея состояла в том, чтобы вручную реализовать метод equals во всех классах и использовать assertEquals.Я отказался от этого подхода, решив, что переопределение equals для глубокого сравнения изменяемых объектов - это плохо, так как вы почти всегда хотите, чтобы коллекции использовали равенство ссылок для изменяемых объектов, которые они содержат [1].
Затем яподумал, что я мог бы просто переименовать метод в contentEquals или что-то еще.Однако, подумав больше, я понял, что это не поможет мне найти тот тип регрессий, который я искал.Если программист добавляет новое (изменяемое) поле и забывает добавить его в метод clone, он, вероятно, забудет добавить его и в метод contentEquals, и все эти регрессионные тесты, которые я пишу, будут бесполезны.
Затем я написал изящную функцию assertContentEquals, которая использует отражение для проверки значения всех (не преходящих) членов объекта, при необходимости рекурсивно.Это позволяет избежать проблем, связанных с методом ручного сравнения, описанным выше, поскольку по умолчанию предполагается, что все поля должны быть сохранены, а программист должен явно объявить поля для пропуска.Однако существуют законные случаи, когда поле действительно не должно быть таким же после клонирования [2].Я добавил дополнительный параметр toassertContentEquals, в котором перечисляются поля, которые следует игнорировать, но поскольку этот список объявлен в модульном тесте, в случае рекурсивной проверки он становится очень уродливым и очень быстрым.
Так что теперь я думаю о возвращении к включению метода contentEquals в каждый тестируемый класс, но на этот раз реализованный с использованием вспомогательной функции, аналогичной описанному выше assertContentsEquals.Таким образом, при рекурсивной работе исключения будут определены в каждом отдельном классе.
Есть комментарии?Как вы подходили к этому вопросу в прошлом?
Отредактировано, чтобы изложить мои мысли:
[1] Я получил рациональное решение не переопределять равные для изменяемых классов изэто статья .Как только вы добавите изменяемый объект в Set / Map, если поле изменится, его хеш изменится, но его корзина не изменится.Таким образом, параметры не должны переопределять equals / getHash для изменяемых объектов или иметь политику никогда не изменять изменяемый объект после его помещения в коллекцию.
Я не упомянул, что я реализую этот регрессионный тест на существующей кодовой базе.В этом контексте меня пугает идея изменить определение equals, а затем найти все случаи, когда это может изменить поведение программного обеспечения.Я чувствую, что могу легко сломать больше, чем исправить.
[2] Одним из примеров в нашей базе кода является графовая структура, где каждому узлу нужен уникальный идентификатор, чтобы использовать его для связи узлов XML, когда в конечном итоге записывается в XML.Когда мы клонируем эти объекты, мы хотим, чтобы идентификатор был другим, но все остальное осталось прежним.Размышляя об этом больше, кажется, что вопросы «этот объект уже находится в этой коллекции» и «определены ли эти объекты одинаково», используют принципиально разные концепции равенства в этом контексте.Первый спрашивает об идентичности, и я хотел бы, чтобы идентификатор был включен, если проводилось глубокое сравнение, а второй - о сходстве, а я не хочу, чтобы этот идентификатор был включен.Это заставляет меня больше полагаться на реализацию метода equals.
Ребята, вы согласны с этим решением или считаете, что внедрение equals - лучший путь?