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

StackOverflow https://stackoverflow.com/questions/34571

  •  09-06-2019
  •  | 
  •  

Вопрос

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

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

Это было полезно?

Решение

Обновить:

Примерно 10 лет спустя, возможно, лучший способ протестировать закрытый метод или любой недоступный участник - это @Jailbreak из самого Многообразие рамки.

@Jailbreak Foo foo = new Foo();
// Direct, *type-safe* access to *all* foo's members
foo.privateMethod(x, y, z);
foo.privateField = value;

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

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

Внутренне мы используем помощников для получения / установки private и private static переменные, а также вызывать 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.Тот Самый setAccessible(true) требуется, чтобы поиграться с рядовыми.

Другие советы

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

<Ол>
  • Закрытый метод - это мертвый код
  • Рядом с классом, который вы тестируете, ощущается запах дизайна
  • Метод, который вы пытаетесь протестировать, не должен быть закрытым
  • Когда у меня есть закрытые методы в классе, которые достаточно сложны, и я чувствую необходимость непосредственно тестировать закрытые методы, это пахнет кодом: мой класс слишком сложен.

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

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

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

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

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

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

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

    • Не тестируйте частные методы.
    • Предоставьте доступ к пакету методов.
    • Используйте вложенный тестовый класс.
    • Используйте рефлексию.

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

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

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

    Таким образом, мой компромисс заключается в том, чтобы усложнить JUnits отражением, а не поставить под угрозу мою безопасность & amp; SDK.

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

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

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

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

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

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

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

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

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

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

    Преимущества:

    • Может тестироваться с более высокой степенью детализации

    Недостатки:

    • Тестовый код должен находиться в том же файле, что и исходный код, что может быть сложнее в обслуживании
    • Аналогично с выходными файлами .class, они должны оставаться в том же пакете, который объявлен в исходном коде

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

    Вот запутанный пример того, как это будет работать:

    // Import statements and package declarations
    
    public class ClassToTest
    {
        private int decrement(int toDecrement) {
            toDecrement--;
            return toDecrement;
        }
    
        // Constructor and the rest of the class
    
        public static class StaticInnerTest extends TestCase
        {
            public StaticInnerTest(){
                super();
            }
    
            public void testDecrement(){
                int number = 10;
                ClassToTest toTest= new ClassToTest();
                int decremented = toTest.decrement(number);
                assertEquals(9, decremented);
            }
    
            public static void main(String[] args) {
                junit.textui.TestRunner.run(StaticInnerTest.class);
            }
        }
    }
    

    Внутренний класс был бы скомпилирован для ClassToTest$StaticInnerTest.

    Смотрите также: Java-совет 106:Статические внутренние классы для развлечения и получения прибыли

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

    <Ол>
  • Сохраняйте все методы небольшими и целенаправленными (легко тестировать, легко находить, что не так)
  • Используйте инструменты покрытия кода. Мне нравится Cobertura (о, счастливый день, похоже, вышла новая версия!)
  • Запустите покрытие кода на модульных тестах. Если вы видите, что методы не полностью протестированы, добавьте тесты, чтобы увеличить охват. Стремитесь к 100% охвату кода, но понимайте, что вы, вероятно, не получите его.

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

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

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

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

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

    ReflectionTestUtils.invokeMethod()
    

    Например:

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

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

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

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

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

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

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

    Так что не проверяйте частные методы.

    Ответ от JUnit.org Страница часто задаваемых вопросов:

    Но если ты должен.....

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

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

    P.S.Я вношу основной вклад в Dp4j, спросить я если тебе нужна помощь.:)

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

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

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

    Закрытый метод доступен только в пределах одного класса. Поэтому нет возможности проверить & # 8220; private & # 8221; метод целевого класса из любого тестового класса. Выход заключается в том, что вы можете выполнить модульное тестирование вручную или изменить свой метод с & # 8220; private & # 8221; на & # 8220; защищенный & # 8221;.

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

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

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

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

    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;
    }
    

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

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

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

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

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

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

    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

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

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

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

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

    Вызов методов, например, закрытый метод void (String s) - с помощью отражения Java

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

    Вызов методов, например, закрытый метод void (String s) - от Picklock

    interface Accessible {
      void method(String s);
    }
    
    ...
    Accessible a = ObjectAccess.unlock(targetObject).features(Accessible.class);
    a.method("mystring");
    

    Настройка полей, например частное количество BigInteger; - по отражению Java

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

    Настройка полей, например частная сумма BigInteger; - от Picklock

    interface Accessible {
      void setAmount(BigInteger amount);
    }
    
    ...
    Accessible a = ObjectAccess.unlock(targetObject).features(Accessible.class);
    a.setAmount(BigInteger.valueOf(42));
    

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

      

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

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

    scroll top