Является ли хорошей практикой использование закрытых методов пакета для облегчения модульных тестов?
Иногда я оказывался в ситуациях, когда модульные тесты были бы проще, если бы я изменил видимость некоторых методов с приватного на приватный пакет, чтобы облегчить имитацию модульного теста, утверждения...
Одним из примеров будет это
Скажем, у меня есть объект A, который содержит 4 атрибута X, Y, Z и R, где X, Y и Z - наборы, а R - отношения между различными элементами каждого набора, например, отношение будет состоять из элемента X, элемент Y и элемент Z. Объект A не позволяет прямой доступ к X, Y, Z или R, вместо этого он предоставляет богатый API, который позволяет создавать новые элементы на X, Y и Z, также позволяет вам смешать эти элементы в новые элементы R Для модульного тестирования было бы очень удобно иметь открытые методы getX(), public getY(), public getZ() и public getR(), чтобы я мог делать точные утверждения о внутренностях объекта каждый раз, когда я вызываю API объекта. Тем не менее, разоблачение X, Y и Z - это то, что я хочу предотвратить, поэтому с самого начала объект делает эти элементы частными и обеспечивает только косвенный доступ к ним с помощью своего API. Однако имеет ли смысл предоставлять закрытые методы пакета getX(), getY(), getZ() и getR (), чтобы по крайней мере сформировать модульный тест, чтобы я мог легко проверить, является ли внутреннее состояние объекта ожидаемым?
Недостатком является, конечно, то, что видимость метода увеличивается, и, учитывая, что такой метод был закрытым по уважительной причине, он выглядит немного странно.
Конечно, я мог бы использовать отражение, чтобы достичь того же, но это кажется еще более грязным.
Итак, вопрос в том, хорошая это или плохая практика? это запах кода? это случается с кем-то еще? Есть ли лучшая техника для этого?
4 ответа
Если вы хотите проверить приватный метод, у вас есть несколько вариантов:
- Внедрение зависимости
Если вам действительно нужно протестировать закрытый метод, вполне возможно, что этот метод на самом деле должен быть общедоступным для другого класса (запах кода). Это означает, что если у вас есть закрытый метод " m " в классе " A ", вы можете рассмотреть вопрос о том, чтобы переместить " m " как открытый метод в классе " B ", а затем внедрить "B" в "A".
Эта техника довольно распространена, и в большинстве случаев она делает свое дело. Тем не менее, это имеет смысл только тогда, когда вы перемещаете значительную часть вашего кода из исходного класса ('A') в новый класс ('B').
Это решение подходит для Mockito.
- Тест Двойного Наследования
Измените видимость частного метода "m" в классе "A" на защищенный. Затем создайте класс "B" в тестовой папке, которая расширяет "A". Наконец, создайте открытый метод 'pm' в 'B' с той же сигнатурой, что и 'm', который просто вызывает 'm'. Таким образом, вы можете "сделать" невидимый метод "m" в "A" доступным, хотя "pm" в "B".
Это решение также подходит для Mockito.
- Groovy
Используйте динамический язык JVM, такой как Groovy, который позволяет вам вызывать закрытые методы.
- отражение
Получите приватный метод, используя отражение, а затем измените его видимость. Смотрите это.
- Видимость пакета
Объявите метод как пакет (без ограничения видимости) и объявите тестовый класс в том же пакете. Мне лично не нравится использовать этот, потому что я не нахожу элегантную видимость пакета Java.
Обычно хорошая практика не раскрывать внутреннюю логику. Вместо этого вы должны сделать свои классы настраиваемыми. Например, если вашему классу нужны другие компоненты, такие как, скажем, HttpComponent или т. Д., Попробуйте использовать различные методы внедрения зависимостей для обеспечения этих зависимостей. Затем в тестах вы можете смоделировать эти зависимости и проверить их.
В вашем случае это зависит от контекста. Большую часть времени вы тестируете закрытые функции как часть тестирования публичных функций. Таким образом, вы проверяете публичное поведение для разных случаев, и если все проходит, значит, частная функция, которая была вызвана через эту публичную функцию, тоже работает.
Общее предложение - протестировать только общедоступный API класса (который вызывает частный API и также проходит тестирование). В противном случае в случае рефакторинга вашего внутреннего API вам потребуется рефакторинг большинства тестов.
Звучит так, будто А делает слишком много вещей, если управляет X,Y,Z и R.
Возможно, хорошей идеей было бы реорганизовать ваш код, чтобы сделать R отдельным классом, который принимает X, Y и Z в качестве входных параметров. Класс может быть сделан пакетом приватным, если хотите. В любом случае, вы сможете напрямую проверить R, указав различные значения X, Y и Z.
Для модульного тестирования у вас могут возникнуть проблемы с тестированием и проверкой частных, статических методов и конструкторов. Таким образом, у вас есть выбор использовать одно из двух решений:
Не используйте приватный метод, и сделайте их видимыми. Также не используйте статические методы и делайте их в одноэлементном классе.
Используйте фиктивную библиотеку, которая выполняет такие тесты, как powermock. Он также может макет конструктора и статический метод.
Плохого или хорошего решения, я полагаю, нет, это зависит от стратегий проекта и от решения принять их для проверки.