Как проверить закрытую функцию или класс, который имеет частные методы, поля или внутренние классы?

Как выполнить модульный тест (используя xUnit) класса, который имеет внутренние частные методы, поля или вложенные классы? Или функция, которая становится частной, имея внутренняя перелинковка (static в C/C++) или в частной (аноним) пространства имен?

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

30 ответов


если у вас есть что-то из наследия Java приложение, и вам не разрешено изменять видимость ваших методов, лучший способ проверить частные методы-использовать отражение.

внутренне мы используем помощников, чтобы получить/set private и private static переменные, а также invoke private и private static методы. Следующие шаблоны позволят вам сделать практически все, что связано с частными методами и полями. Конечно, ты не можешь измениться. private static final переменные через отражение.

Method method = targetClass.getDeclaredMethod(methodName, argClasses);
method.setAccessible(true);
return method.invoke(targetObject, argObjects);

и для полей:

Field field = targetClass.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(object, value);

Примечания:
1. targetClass.getDeclaredMethod(methodName, argClasses) позволяет заглянуть в private методы. То же самое относится и к getDeclaredField.
2. The setAccessible(true) требуется, чтобы играть с рядовыми.


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

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

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

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

новый класс предоставляет эти методы как "публичные", поэтому они доступны для модульного тестирования. Новые и старые классы теперь проще, чем исходный класс, что отлично подходит для меня (мне нужно держать вещи простыми, или я потеряюсь!).

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


Я использовал отражение сделать это для Java в прошлом, и, на мой взгляд, это была большая ошибка.

строго говоря, вы должны не писать модульные тесты, которые непосредственно тестируют частные методы. Что ты!--5-->должны be testing-это публичный контракт, который класс имеет с другими объектами; вы никогда не должны напрямую тестировать внутренние объекты объекта. Если другой разработчик хочет внести небольшое внутреннее изменение в класс, которое не повлияйте на публичный контракт классов, затем он должен изменить тест на основе отражения, чтобы убедиться, что он работает. Если вы делаете это неоднократно на протяжении всего проекта, модульные тесты перестают быть полезным измерением работоспособности кода и начинают становиться препятствием для разработки и раздражением для команды разработчиков.

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


из этой статьи: тестирование частных методов с JUnit и SuiteRunner (Билл Веннерс), у вас в основном есть 4 варианта:

  • не тестировать приватные методы.
  • дайте доступ к пакету методов.
  • используйте вложенный тестовый класс.
  • использовать отражение.

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


всего два примера того, где я хотел бы протестировать частный метод:

  1. дешифрования - Я бы не стал хотите сделать их видимыми для всех, чтобы увидеть только для ради тестирования, иначе любой может используйте их для расшифровки. Но они встроенный в код, сложный, и нужно всегда работать (очевидным исключением является отражение, которое можно использовать для просмотра даже частных методов в большинстве случаев, когда SecurityManager не настроен для предотвращения этот.)
  2. создание SDK для сообщества потребление. Здесь публика берет на себя совершенно другое значение, так как это это код, который может увидеть весь мир. (не только внутренний для моего приложения). Я поставил код в частные методы, если я этого не делаю хотите, чтобы пользователи SDK видели его-I не рассматривайте это как запах кода, просто как работает Программирование SDK. Но конечно, мне все еще нужно проверить частные методы, и они где функциональность моего SDK на самом деле жизни.

Я понимаю идею только тестирования "контракта". Но я не вижу, можно ли на самом деле не тестировать код - ваш пробег может отличаться.

таким образом, мой компромисс включает в себя усложнение юнитов с отражением, а не компрометацию моей безопасности и SDK.


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


другой подход, который я использовал, - это изменить частный метод на пакет private или protected, а затем дополнить его @VisibleForTesting аннотация библиотеки Google Guava.

это скажет любому, кто использует этот метод, чтобы проявлять осторожность и не обращаться к нему напрямую даже в пакете. Также тестовый класс не обязательно должен быть в одном пакете физически, но в том же пакете под тест.

например, если метод для тестирования находится в src/main/java/mypackage/MyClass.java затем тестовый вызов должен быть помещен в src/test/java/mypackage/MyClassTest.java. Таким образом, вы получили доступ к методу тестирования в своем тестовом классе.


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

Я использую junitx.утиль.PrivateAccessor-пакет для Java . Много полезных однострочных для доступа к частным методам и частным полям.

import junitx.util.PrivateAccessor;

PrivateAccessor.setField(myObjectReference, "myCrucialButHardToReachPrivateField", myNewValue);
PrivateAccessor.invoke(myObjectReference, "privateMethodName", java.lang.Class[] parameterTypes, java.lang.Object[] args);

попробовав Cem Catikkas'решение с помощью отражения для Java я бы сказал, что его решение было более элегантным, чем я описал здесь. Однако, если вы ищете альтернативу использованию отражения и имеете доступ к тестируемому источнику, это все равно будет вариантом.

существует возможная заслуга в тестировании частных методов класса, особенно с


Как говорили другие... не тестируйте частные методы напрямую. Вот несколько мыслей:--1-->

  1. держите все методы небольшими и сосредоточенными (легко проверить, легко найти, что не так)
  2. используйте инструменты покрытия кода. Мне нравится Cobertura (о счастливый день, похоже, новая версия вышла!)

запустите покрытие кода в модульных тестах. Если вы видите, что методы не полностью протестированы, добавьте в тесты, чтобы получить покрытие. Цель для кода 100% покрытие, но поймите, что вы, вероятно, не получите его.


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

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

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

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


при использовании Spring,ReflectionTestUtils предоставляет некоторые удобные инструменты, которые помогают здесь с минимальными усилиями. Например, чтобы настроить макет для частного члена без необходимости добавления нежелательного публичного сеттера:

ReflectionTestUtils.setField(theClass, "theUnsettableField", theMockObject);

Если вы пытаетесь проверить существующий код, который вы не хотите или не можете изменить, рефлексия-это хороший выбор.

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

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


на Весенние Рамки вы можете проверить частные методы, используя этот метод:

ReflectionTestUtils.invokeMethod()

например:

ReflectionTestUtils.invokeMethod(TestClazz, "createTest", "input data");

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

поэтому не тестируйте частные методы.


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


Если вы используете JUnit, посмотрите на junit-аддоны. Он имеет возможность игнорировать модель безопасности Java и получать доступ к частным методам и атрибутам.


Я не склонен тестировать частные методы. Есть безумие. Лично я считаю, что вы должны тестировать только публично открытые интерфейсы (и это включает защищенные и внутренние методы).


ответ от JUnit.org страница FAQ:

но если нужно...

Если вы используете JDK 1.3 или выше, вы можете использовать отражение для подрыва механизм управления доступом с помощью PrivilegedAccessor. Подробнее о том, как его использовать, читайте:!--10-->в этой статье.

Если вы используете JDK 1.6 или выше и аннотируете свои тесты с помощью @Test, вы можете использовать Dp4j для того чтобы впрыснуть отражение в ваших методах тестирования. Для подробности о том, как его использовать, см. этот тестовый скрипт.

P. S. Я главный Contributor к Dp4j, задать меня Если вам нужна помощь. :)


закрытый метод должен быть доступен только в том же классе. Таким образом, нет способа проверить "частный" метод целевого класса из любого тестового класса. Выход заключается в том, что вы можете выполнить модульное тестирование вручную или изменить свой метод с "частного" на "защищенный".

и тогда защищенный метод может быть доступен только в том же пакете, где определен класс. Таким образом, тестирование защищенного метода целевого класса означает, что нам нужно определить ваш тестовый класс в том же пакете в качестве целевого класса.

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


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

вы упомянули различные типы проблем. Начнем с частных полей. В случае частных полей я бы добавил новый конструктор и ввел в него поля. Вместо этого:

public class ClassToTest {

    private final String first = "first";
    private final List<String> second = new ArrayList<>();
    ...
}

Я бы использовал этот:

public class ClassToTest {

    private final String first;
    private final List<String> second;

    public ClassToTest() {
        this("first", new ArrayList<>());
    }

    public ClassToTest(final String first, final List<String> second) {
        this.first = first;
        this.second = second;
    }
    ...
}

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

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

public ClassToTest() {
    this(...);
}

public ClassToTest(final Callable<T> privateMethodLogic) {
    this.privateMethodLogic = privateMethodLogic;
}

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

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

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


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

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


вот моя общая функция для проверки частных полей:

protected <F> F getPrivateField(String fieldName, Object obj)
    throws NoSuchFieldException, IllegalAccessException {
    Field field =
        obj.getClass().getDeclaredField(fieldName);

    field.setAccessible(true);
    return (F)field.get(obj);
}

пожалуйста, см. ниже для примера;

необходимо добавить следующий оператор импорта:

import org.powermock.reflect.Whitebox;

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

Whitebox.invokeMethod(obj, "privateMethod", "param1");

сегодня я нажал библиотеку Java, чтобы помочь тестировать частные методы и поля. Он был разработан с учетом Android, но его действительно можно использовать для любого проекта Java.

Если у вас есть код с частными методами, полями или конструкторами, вы можете использовать BoundBox. Это именно то, что вы ищете. Ниже приведен пример теста, который обращается к двум частным полям активности Android для тестирования это:

@UiThreadTest
public void testCompute() {

    // Given
    boundBoxOfMainActivity = new BoundBoxOfMainActivity(getActivity());

    // When
    boundBoxOfMainActivity.boundBox_getButtonMain().performClick();

    // Then
    assertEquals("42", boundBoxOfMainActivity.boundBox_getTextViewMain().getText());
}

BoundBox упрощает тестирование частных / защищенных полей, методов и конструкторов. Вы даже можете получить доступ к вещам, которые скрыты по наследству. Действительно, BoundBox нарушает инкапсуляцию. Это даст вам доступ ко всему этому через размышление,но все проверяется во время компиляции.

Он идеально подходит для тестирования некоторого устаревшего кода. Используйте его осторожно. ;)

https://github.com/stephanenicolas/boundbox


во-первых, я выброшу этот вопрос: почему ваши частные члены нуждаются в изолированном тестировании? Являются ли они настолько сложными, обеспечивая такое сложное поведение, которое требует тестирования отдельно от публичной поверхности? Это модульное тестирование, а не тестирование "строки кода". Не переживай по пустякам.

Если они настолько велики, что эти частные члены являются "единицей" большой сложности-рассмотрите рефакторинг таких частных членов из этого класса.

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


недавно у меня была эта проблема и я написал небольшой инструмент, называемый отмычка, что позволяет избежать проблем явного использования API отражения Java, два примера:

вызов методов, например,private void method(String s) - по Java reflection

Method method = targetClass.getDeclaredMethod("method", String.class);
method.setAccessible(true);
return method.invoke(targetObject, "mystring");

вызов методов, например,private void method(String s) - С помощью отмычки

interface Accessible {
  void method(String s);
}

...
Accessible a = ObjectAccess.unlock(targetObject).features(Accessible.class);
a.method("mystring");

настройки полей, например,private BigInteger amount; - по Java отражение!--14-->

Field field = targetClass.getDeclaredField("amount");
field.setAccessible(true);
field.set(object, BigInteger.valueOf(42));

настройки полей, например,private BigInteger amount; - С помощью отмычки

interface Accessible {
  void setAmount(BigInteger amount);
}

...
Accessible a = ObjectAccess.unlock(targetObject).features(Accessible.class);
a.setAmount(BigInteger.valueOf(42));

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

вы не можете использовать отражение, чтобы получить частные методы вне класса владельца, частный модификатор влияет на отражение также

Это не правда. Вы большинство конечно, может, как упоминалось в ответ джема Катикки.