Добрый день! Статья будет про один совершенно незначительный момент в кодировании.
Например, многие мои коллеги сказали - Doch, das ist ganz normal! "А, чё, все норм, нашел к чему придраться!" Но, тем не менее.
Есть несколько вещей в написании кода, которые мне не нравятся. Ну, на самом деле не несколько, чем старше становлюсь, тем больше и больше таких вещей, характер не лучшает. Но в топе - когда девелоперы пишут тесты к private методам. А, нет! Когда они ради этого убирают слово private у метода и метод становится package-private.
Вот, ты такой ревьювишь класс, строк этак на 3К, написанный в лучших традициях процедурного программирования, а там 10 методов public, 10 private, а еще 15 package-private. И про интерфейсы мы не слышали - это ж сервис, давай так прям так и инжектить! Ок, думаешь ты, сейчас рукава засучим, вынесем все public методы в интерфейс, helper-ы, их в отдельный package - короче распилим это безумство на части согласно SRP. Но тут же обнаруживаешь, что package-private методы давно дергаются из других мест. Дык у нас все классы в один package свалены! If something can be misused, it will be misused for sure, как говорится.
Да-да, все знают, писать тесты к private методам не надо, если ты это делаешь, то что-то не так с дизайном, и так далее. Но, блин, люди все равно это делают, и надо сказать, не без причин. Представьте, что упомянутый выше класс вообще был с zero test coverage. А рефакторить надо. Вот так и появляются package-private методы. Но как же нам быть - вон и Whitebox из Mockito выпилили, конечно в PowerMock он остался, но ради этого тащить PowerMock в проект, да и синтаксис не лучше прямого вызова через reflection. Можно конечно свой велосипед написать, но... И так и так не айс получается.
А вот представьте, что для доступа к private элементам класса - методам, полям, у нас есть интерфейс. Что за фигня, вы скажете, совсем парень заработался. А вот так:
Есть класс:
public class ObjectWithPrivates { private final AtomicInteger count = new AtomicInteger(0); private String name; private String methodToTest(String in) { return name + in + count.incrementAndGet(); } }
Тест:
interface TestPrivates { void setName(String name); String methodToTest(String in); AtomicInteger getCount(); } @Test void testPrivates() { ObjectWithPrivates obj = new ObjectWithPrivates(); TestPrivates objWithPrivates = API.lookupPrivatesIn(obj) .usingInterface(TestPrivates.class); objWithPrivates.setName("Andromeda"); String in = objWithPrivates.methodToTest("in"); AtomicInteger count = objWithPrivates.getCount(); Assertions.assertEquals("Andromedain1", in); Assertions.assertEquals(1, count.get()); }
Как видите, с помощью интерфейса мы можем дергать private методы, и получать доступ к private полям. Ну и по коду сразу можно сказать, что происходит. Вы делаете интерфейс прям в тесте, добавляее методы совпадающие по сигнатуре с private методами и getters/setters для private полей и как будто кастите свой объект к этому интерфейсу.
Имейте ввиду, что все манипуляции происходят с obj и можно, например, засетить все private поля у obj и потом дернуть public метод.
Можно получить доступ к private полям/методам родительского класса:
public class ObjectWithPrivates { private static String name; } public class ObjectWithPrivatesSub extends ObjectWithPrivates { private static String name; } interface TestPrivates { void setName(String name); } @Test void testPrivates() { ObjectWithPrivatesSub obj = new ObjectWithPrivatesSub(); TestPrivates accessToPrivates = API.lookupPrivatesIn(obj) .lookupInSuperclass() .usingInterface(TestPrivates.class); accessToPrivates.setName("Vasya Pupkin"); : }
А можно и статические методы/поля дергать:
public class ObjectWithPrivates { private static String name; private static String whatIsYourName(){ return name; } } interface TestPrivates { void setName(String name); private String whatIsYourName(){ } @Test void testPrivates() { TestPrivates accessToPrivates = API.lookupPrivatesIn(ObjectWithPrivates.class) .usingInterface(TestPrivates.class); accessToPrivates.setName("Vasya Pupkin"); Assertions.assertEquals("Vasya Pupkin", accessToPrivates.whatIsYourName()); : }
А можно и объект создать через private конструктор.
public class ClassC { : : private ClassC(int a, String b, Object c, Long d) { : } @Test void createObject() { ClassC classC = API.createInstanceOf(ClassC.class) .withArguments(new Integer(5), "yo!", new Long(123L), 15L); : }
При этом анализируется количество параметром и их типы и ищется подходящий конструктор.
Под капотом там стандартный reflection и Dynamic Proxy, никаких дополнительных зависимостей.
Вот ссылка на проект testprivates. Использовать только для тестов.
Всех с наступающим, не болейте!