Модульное тестирование на неизменность объекта - PullRequest
9 голосов
/ 19 июля 2011

Я хочу убедиться, что данная группа объектов неизменна.

Я думал о чем-то вроде:

  1. проверить, является ли каждое поле приватным. Final
  2. проверить, является ли класс окончательным
  3. проверка на изменчивость

Итак, я думаю, мой вопрос: 3. Возможно ли?

Я могу рекурсивно проверить, есть ли у каждого члена класса свои поля private final, но этого недостаточно, поскольку у класса может быть метод e с именем getHaha(param), который добавляет данный параметр в массив, например.

Так есть ли хороший способ проверить, является ли объект неизменным или это вообще возможно?

Спасибо

Ответы [ 6 ]

5 голосов
/ 17 октября 2011

Вы можете проверить этот проект:

Детектор изменчивости

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

Отказ от ответственности: я написал это; -)

3 голосов
/ 19 июля 2011

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

Ваша проблема в том, что существуют разные формы неизменности. Даже String провалит ваш тест Являются ли String, Date, Method неизменяемыми? Таким образом, вы можете доказать, что класс является строго неизменным, но вам, скорее всего, будет лучше генерировать свою модель данных.

2 голосов
/ 23 января 2015

Да, вы можете написать детектор неизменности.

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

Неизменяемость класса имеет четыре возможных значения: Unknown, Mutable, Immutable и Calculating. Возможно, вы захотите иметь карту, которая связывает каждый класс, с которым вы встречались, до значения неизменяемости. Конечно, Unknown на самом деле не нужно реализовывать, поскольку это будет подразумеваемое состояние любого класса, которого еще нет на карте.

Итак, когда вы начинаете изучение класса, вы связываете его со значением Calculating на карте, а когда вы закончите, вы заменяете Calculating либо Immutable, либо Mutable.

Для каждого класса вам нужно только проверять членов поля, а не код. Идея проверки байт-кода довольно ошибочна.

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

Вы должны не проверить, является ли поле закрытым; для класса совершенно нормально иметь открытое поле, и видимость поля никоим образом не влияет на неизменность декларирующего класса, формы или формы. Вам нужно только проверить, является ли поле окончательным и его тип неизменен.

При изучении класса в первую очередь вы хотите выполнить повторение, чтобы определить неизменность класса super. Если супер является изменяемым, то потомок по определению тоже изменяемый.

Тогда вам нужно только проверить объявленные поля класса, а не все поля.

Если поле не является окончательным, тогда ваш класс изменчив.

Если поле является окончательным, но тип поля является изменяемым, то ваш класс является изменяемым. (Массивы по определению являются изменяемыми.)

Если поле является окончательным и тип поля Calculating, игнорируйте его и переходите к следующему полю. Если все поля являются неизменяемыми или Calculating, то ваш класс неизменен.

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

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

  1. Добавление классов (и интерфейсов) вручную в ваш детектор неизменяемости.Если вы знаете, что определенный класс является эффективно неизменяемым, несмотря на то, что тест на неизменяемость для него не пройден, вы можете вручную добавить запись в ваш детектор, которая связывает его с Immutable.Таким образом, детектор никогда не будет пытаться проверить, является ли он неизменным, он всегда просто скажет «да, это так».

  2. Введение аннотации @ImmutabilityOverride.Ваш детектор неизменяемости может проверять наличие этой аннотации на поле, и, если он присутствует, он может рассматривать поле как неизменяемое, несмотря на то, что поле может быть не окончательным или его тип может быть изменяемым.Детектор также может проверять наличие этой аннотации в классе, таким образом рассматривая класс как неизменный, даже не удосужившись проверить его поля.

Надеюсь, это поможет будущим поколениям.

1 голос
/ 19 июля 2011

Этот поток может помочь Как идентифицировать неизменяемые объекты в Java . Взгляните на второй популярный ответ, возможно, можно проверить наличие проблем с неизменяемостью в FindBugs. Если вы запускаете его при каждом коммите, вы можете назвать его модульным тестом:)

EDIT

Кажется, что FindBugs проверяет только на final, это немного. Вы можете реализовать свое собственное правило в соответствии с вашими шаблонами и классами, которые вы используете в коде.

1 голос
/ 19 июля 2011

Уверен, что это невозможно.Рассмотрим следующую функцию:

public void doSomething() {
    if (System.currentTimeMillis() % 100000 == 0) {
        this.innerMember.changeState();
    }
}

Во-первых, вы не сможете обнаружить ее, запустив каждую функцию класса, поскольку эта функция точно изменяет состояние объекта только один раз в 100 секунд.

Во-вторых, вы не сможете обнаружить его при разборе кода, поскольку вы не знаете, если функция changeState() изменяет состояние innerMember или нет.

1 голос
/ 19 июля 2011

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

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