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

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

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

Лучший способ протестировать частный метод - не тестировать его напрямую

Surya 31.08.2010 00:43

Проверьте статью Тестирование частных методов с помощью JUnit и SuiteRunner.

Mouna Cheikhna 13.10.2010 13:34
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
2 869
2
961 126
55
Перейти к ответу Данный вопрос помечен как решенный

Ответы 55

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

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

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

grinch 14.02.2013 00:29

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

Erik Madsen 25.06.2013 11:53

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

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

Dan Haynes 17.05.2013 18:38

так что не пишите частные методы, просто создайте 10 других небольших классов?

razor 19.05.2015 19:42

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

Akshat Sharda 12.02.2020 17:53

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

Верный факт. Проблема в том, что реализация более сложная, например, если один общедоступный метод вызывает несколько частных методов. Какой из них потерпел неудачу? Или, если это сложный частный метод, который нельзя раскрыть или передать в новый класс

Bernardo Dal Corno 09.05.2018 21:11

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

Wei Qiu 19.06.2018 06:04

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

  1. Don't test private methods.
  2. Give the methods package access.
  3. Use a nested test class.
  4. Use reflection.

@JimmyT., Зависит от того, для кого предназначен "производственный код". Я бы назвал производственным кодом код, созданный для приложений, размещенных в VPN, целевыми пользователями которых являются системные администраторы.

Pacerier 21.08.2015 10:03

@Pacerier Что ты имеешь в виду?

Jimmy T. 21.08.2015 23:27

5-й вариант, как упомянуто выше, - это тестирование общедоступного метода, вызывающего частный метод? Правильный?

user2441441 13.07.2016 00:05

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

user2918461 20.09.2017 18:32

Мне очень нравится тест с отражением: Здесь шаблон Method = targetClass.getDeclaredMethod (methodName, argClasses); method.setAccessible (правда); return method.invoke (targetObject, argObjects);

Aguid 28.10.2017 00:13

@ user2441441 то же самое, что и # 1 - не тестируйте частные методы

Trenton 23.04.2020 02:33

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

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

you can't use reflection to get private methods from outside the owner class, the private modifier affects reflection also

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

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

juan 29.08.2008 20:34

@jmfsg В статье, на которую вы ссылаетесь, конкретно говорится: «Тестирование частных методов требует немного больше усилий, но мы все равно можем сделать это с помощью System.Reflection». (Очевидно, вам нужен ReflectionPermission, но обычно это не проблема.)

tc. 23.03.2013 00:03
Ответ принят как подходящий

Update:

Some 10 years later perhaps the best way to test a private method, or any inaccessible member, is via @Jailbreak from the Manifold framework.

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

This way your code remains type-safe and readable. No design compromises, no overexposing methods and fields for the sake of tests.

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

Внутри мы используем помощники для получения / установки переменных 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);

Notes:
1. TargetClass.getDeclaredMethod(methodName, argClasses) lets you look into private methods. The same thing applies for getDeclaredField.
2. The setAccessible(true) is required to play around with privates.

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

andygavin 26.06.2009 18:23

Попробовав Cem Catikkas решение с использованием отражения для 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: статические внутренние классы для развлечения и получения прибыли

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

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

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

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

Как часть библиотеки jmockit, у вас есть доступ к классу Deencapsulation, который упрощает тестирование частных методов: Deencapsulation.invoke(instance, "privateMethod", param1, param2);

Domenic D. 19.11.2013 08:22

Я использую этот подход все время. Очень полезно

MedicineMan 20.11.2017 08:38

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

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

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

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

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

Dainius 03.08.2012 15:40

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

Dainius 04.08.2012 20:35

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

Jay Bazuzi 05.08.2012 01:15

Но вы согласны с тем, что хороший OOD будет предоставлять (делать общедоступными) только те методы, которые необходимы для правильной работы этого класса / объекта? все остальные должны быть частными / protectec. Так что в некоторых частных методах будет некоторая логика, и тестирование этих методов IMO только улучшит качество программного обеспечения. Конечно, я согласен с тем, что если какой-то фрагмент кода слишком сложен, его следует разделить на отдельные методы / классы.

Dainius 06.08.2012 10:53

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

confusopoly 18.06.2013 01:59

То, что вы описываете, является постоянной проблемой для всех, кто пытается выполнить модульное тестирование, и выходит далеко за рамки этого вопроса. Короткий ответ: «Прочтите Эффективная работа с устаревшим кодом Майкла Фезерса, а затем помните, что весь код является устаревшим кодом, через 5 минут после того, как вы его напишете.

Jay Bazuzi 18.06.2013 04:04

@Danius: добавление нового класса в большинстве случаев снижает сложность. Просто измерьте это. Возможность тестирования в некоторых случаях борется с OOD. Современный подход основан на проверяемости.

AlexWien 02.07.2013 00:54

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

Charles 12.10.2014 18:23

@Charles Но, конечно, все частные методы уже протестированы с помощью общедоступных методов. Утверждение должно гласить: «достаточно сложный, чтобы я чувствовал необходимость протестировать его в изоляции».

biziclop 23.07.2015 23:25

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

c-garcia 13.09.2015 11:37

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

pnschofield 03.12.2016 00:35

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

John Donn 27.07.2017 14:25

@JohnDonn Конечно. Но «Запах кода» не означает «вы должны изменить код следующим образом». Это означает «вот намек на место, где вы могли бы улучшить ситуацию». Вам решать, найти правильное изменение / судить, стоит ли ваша идея по улучшению, и решить, подходящее ли время для реализации этого изменения.

Jay Bazuzi 31.08.2017 08:33

Тестировать частные методы - это нормально. Комментарии о том, что если мы тестируем частные методы, дизайн плохой, являются иррациональными. Можно применять тестирование на разных уровнях: на внутреннем уровне класса (частные / защищенные методы), уровне класса (общедоступные методы), уровне модуля, уровне системы и т. д.

qartal 21.11.2019 18:37

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

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

Не стоит косвенно тестировать! Не только прикосновение через покрытие; проверьте, что ожидаемый результат доставлен!

AlexWien 02.07.2013 00:51

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

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

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

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

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

oxbow_lakes 15.02.2009 20:33

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

AlexWien 02.07.2013 00:48

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

Так что не тестируйте частные методы.

unit test и src code - это пара. Если вы измените src, возможно, вам придется изменить unit test. В этом смысл теста юнит. Они должны гарантировать, что все работает, как прежде. и это нормально, если они сломаются, если вы измените код.

AlexWien 02.07.2013 00:43

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

Harald Coppoolse 09.11.2015 16:27

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

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

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

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

coolcfan 09.08.2012 06:20

Я видел классы, в которых единственным общедоступным методом является main [], и они открывают графический интерфейс, а также подключают базу данных и пару веб-серверов по всему миру. Легко сказать «не тестируйте косвенно» ...

Audrius Meskauskas 12.12.2014 11:43

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

Это делает вызов setAccessible(true).

Единственное ограничение состоит в том, что ClassLoader может запретить вам это делать.

См. Подрыв защиты доступа Java для модульного тестирования (Росс Бертон), чтобы узнать, как это сделать на Java.

В C# вы могли бы использовать System.Reflection, хотя в Java я не знаю. Хотя я все равно чувствую желание ответить на этот вопрос, поскольку, если вы «чувствуете, что вам нужно провести модульное тестирование частных методов», я предполагаю, что есть что-то еще, что не так ...

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

Что, если ваши тестовые классы находятся в том же пакете, что и класс, который должен быть протестирован?

Но, конечно, в другом каталоге, src и classes для вашего исходного кода, test/src и test/classes для ваших тестовых классов. И пусть classes и test/classes будут в вашем пути к классам.

тесты могут находиться в разных папках (например, src / test / java и код приложения в src / main / java), но пакет должен быть таким же.

razor 19.05.2015 19:40

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

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

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

+1 к этому. На мой взгляд, это лучший ответ на вопрос. Тестируя частные методы, вы тестируете реализацию. Это противоречит цели модульного тестирования, которое заключается в проверке входов / выходов контракта класса. Тест должен Только знать о реализации достаточно, чтобы имитировать методы, которые он вызывает в своих зависимостях. Ничего больше. Если вы не можете изменить свою реализацию без изменения теста, скорее всего, ваша стратегия тестирования плохая.

Colin M 08.07.2013 17:27

@Colin M Это не совсем то, о чем он спрашивает;) Пусть он сам решает, вы не знаете проект.

Aaron Marcus 02.04.2015 19:21

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

ACV 30.11.2017 14:32

Я согласен. Вы должны проверить, выполняется ли договор. Не стоит проверять, как это делается. Если контакт выполняется без достижения 100% покрытия кода (в частных методах), это может быть мертвый или бесполезный код.

wuppi 02.07.2019 10:43

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

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

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

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

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

ngreen 10.04.2014 07:45

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

Richard Le Mesurier 10.04.2014 14:06

Я комментировал ваш ответ, в частности пункт 1, а не OP. Нет необходимости делать метод закрытым только потому, что вы не хотите, чтобы он был общедоступным.

ngreen 10.04.2014 20:30

@ngreen true thx - я поленился со словом "паблик". Я обновил ответ, чтобы включить общедоступный, защищенный, по умолчанию (и упомянуть об отражении). Я пытался сказать, что есть веская причина для того, чтобы какой-то код был секретным, однако это не должно помешать нам его протестировать.

Richard Le Mesurier 10.04.2014 20:46

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

Bernardo Dal Corno 09.05.2018 21:00

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

Nether 10.05.2020 20:59

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

Я использую 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);

Обязательно загрузите весь пакет JUnit-addons (sourceforge.net/projects/junit-addons), а не PrivateAccessor проекта, рекомендованного Source Forge.

skia.heliou 15.01.2015 16:12

И убедитесь, что когда вы используете Class в качестве первого параметра в этих методах, вы получаете доступ только к членам static.

skia.heliou 20.01.2015 22:40

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

But if you must...

If you are using JDK 1.3 or higher, you can use reflection to subvert the access control mechanism with the aid of the PrivilegedAccessor. For details on how to use it, read this article.

If you are using JDK 1.6 or higher and you annotate your tests with @Test, you can use Dp4j to inject reflection in your test methods. For details on how to use it, see this test script.

P.S. Я главный участник Dp4j, спросите мне, если вам нужна помощь. :)

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

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

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

Я не изменяю видимость метода, который хочу протестировать, и не добавляю дополнительный метод. Вместо этого я добавляю дополнительный общедоступный метод для каждого частного метода, который хочу протестировать. Я называю этот дополнительный метод Test-Port и обозначаю его префиксом t_. Затем этот метод Test-Port просто обращается к соответствующему частному методу.

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

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

Это взлом, а не реальное решение.

givanse 13.07.2012 18:49

@givanse Я не согласен ... это одна из причин, по которой тестирование Java-кода с использованием Groovy настолько эффективно.

Jeff Olson 07.05.2013 21:34

JML имеет синтаксис аннотации комментариев spec_public, который позволяет вам указать метод как общедоступный во время тестов:

private /*@ spec_public @*/ int methodName(){
...
}

Этот синтаксис обсуждается в http://www.eecs.ucf.edu/~leavens/JML/jmlrefman/jmlrefman_2.html#SEC12. Также существует программа, переводящая спецификации JML в тесты JUnit. Я не уверен, насколько хорошо это работает и каковы его возможности, но, похоже, в этом нет необходимости, поскольку JML сам по себе является жизнеспособной средой тестирования.

Сегодня я запустил библиотеку 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

Только что попробовал, BoundBox - простое и элегантное решение!

forhas 02.10.2013 18:12

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

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

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

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

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

Bill K 01.10.2016 00:51

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

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

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

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

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

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

Sayo Oladeji 31.05.2015 05:04

На самом деле в Java нет «Friendly», это термин «Package», и на это указывает отсутствие модификатора private / public / protected. Я бы просто исправил этот ответ, но есть действительно хороший ответ, который уже говорит об этом, поэтому я просто рекомендую удалить его.

Bill K 01.10.2016 00:48

Я чуть не проголосовал против, все время думая: «Это просто неправильный ответ». пока я не добрался до самого последнего предложения, которое противоречит остальному ответу. Последнее предложение должно было быть первым.

Loduwijk 09.03.2017 21:54

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

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

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

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

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

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

interface Accessible {
  void method(String s);
}

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

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

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

Поля настройки, например private BigInteger amount; - от Picklock

interface Accessible {
  void setAmount(BigInteger amount);
}

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

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

ReflectionTestUtils.invokeMethod()

Например:

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

На данный момент самое чистое и краткое решение, но только если вы используете Spring.

Denis Nikolaenko 15.03.2020 06:30

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

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

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

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

Seb 31.01.2017 13:25

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

Fr Jeremy Krieg 06.11.2017 02:15

@FrJeremyKrieg - что это значит?

MasterJoe 10.03.2020 20:14

@ MasterJoe2: цель испытания на огнестойкость - повысить безопасность вашего здания от риска возгорания. Но вы должны предоставить надзирателю доступ к своему зданию. Если вы навсегда оставите входную дверь незапертой, вы увеличите риск несанкционированного доступа и сделаете ее меньше безопасной. То же самое и с шаблоном VisibleForTesting - вы увеличиваете риск несанкционированного доступа, чтобы снизить риск «пожара». Лучше предоставлять одноразовый доступ для тестирования только тогда, когда это необходимо (например, с помощью отражения), чем оставлять его постоянно разблокированным (не закрытым).

Fr Jeremy Krieg 12.03.2020 03:00

См. Пример ниже;

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

import org.powermock.reflect.Whitebox;

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

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

Быстрое добавление к комментарию @Cem Catikka при использовании ExpectedException:

Имейте в виду, что ваше ожидаемое исключение будет заключено в InvocationTargetException, поэтому, чтобы добраться до вашего исключения, вам нужно будет выбросить причину полученного вами InvocationTargetException. Что-то вроде (тестирование частного метода validateRequest () на BizService):

@Rule
public ExpectedException thrown = ExpectedException.none();

@Autowired(required = true)
private BizService svc;


@Test
public void testValidateRequest() throws Exception {

    thrown.expect(BizException.class);
    thrown.expectMessage(expectMessage);

    BizRequest request = /* Mock it, read from source - file, etc. */;
    validateRequest(request);
}

private void validateRequest(BizRequest request) throws Exception {
    Method method = svc.getClass().getDeclaredMethod("validateRequest", BizRequest.class);
    method.setAccessible(true);
    try {
        method.invoke(svc, request);
    }
    catch (InvocationTargetException e) {
        throw ((BizException)e.getCause());
    }
 }

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

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

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

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

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

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

Не согласен с впрыском ClassToTest(Callable). Это усложняет ClassToTest. Будь проще. Кроме того, это требует, чтобы кто-то еще сообщил ClassToTest что-то, чем ClassToTest должен быть хозяином. Здесь есть место для введения логики, но это не то. Вы только что сделали класс труднее, а не проще.

Loduwijk 09.03.2017 21:46

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

Loduwijk 09.03.2017 21:49

Не могли бы вы объяснить, почему это затрудняет поддержку класса ClassToTest? На самом деле это упрощает обслуживание вашего приложения. Что вы предлагаете создать новый класс для каждого другого значения, которое вам понадобится в first и «вторых» переменных?

GROX13 09.03.2017 21:52

Методика тестирования не усложняет. Просто ваш класс плохо написан, настолько плохо, что его невозможно протестировать.

GROX13 09.03.2017 21:54

Сложнее поддерживать, потому что класс сложнее. class A{ A(){} f(){} } проще, чем class A { Logic f; A(logic_f) } class B { g() { new A( logic_f) } }. Если бы это было неправдой, и если бы было правдой, что предоставление логики класса в качестве аргументов конструктора было легче поддерживать, тогда мы бы передали всю логику как аргументы конструктора. Я просто не понимаю, как можно утверждать, что class B{ g() { new A( you_dont_know_your_own_logic_but_I_do ) } } когда-либо упрощает обслуживание A. Бывают случаи, когда подобное введение имеет смысл, но я не считаю ваш пример "более простым в обслуживании".

Loduwijk 10.03.2017 00:59

Что касается «что вы предлагаете создать новый класс для каждого другого необходимого вам значения» ... я не понимаю, как это следует. Нет. Я говорю, что все будет проще. class A { M m; N n; f() { } } супер просто. Не отказывайтесь от этого, если у вас нет причины. Если вам нужно было протестировать приватных членов, конечно, прекрасно; Я просто не понимаю, как вы можете утверждать, что его «легче поддерживать». Внедрение зависимостей упрощает обслуживание только тогда, когда оно снижает сложность (разделяет классы, упрощает отслеживание графиков / диаграмм, что нибудь), чего не было в вашем примере.

Loduwijk 10.03.2017 01:06

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

Loduwijk 10.03.2017 01:07

На самом деле, при третьем (или четвертом?) Чтении я вижу, что, возможно, упустил момент. В вашем public ClassToTest() { this(...); } без аргументов подразумевается, что обычно используется конструктор класса без аргументов, что он может предоставить свою собственную логику, известную внутри класса, а другой конструктор использовал Только для тестирования (или если B хочет изменить свой поведение по какой-то причине; удобный побочный эффект)? Я не уловил, что первая пара перечитывает.

Loduwijk 10.03.2017 01:13

Да, теперь я понял. Тот факт, что у вас нет реализации по умолчанию, известной изнутри ClassToTest, сбил меня с толку, но у вас есть это подразумевается эллипсом. По какой-то причине я изначально думал, что вы предполагаете, что логика должна поставляться только извне. Для таких лохов, как я, не могли бы вы просто отредактировать "...", сделав что-то вроде "defaultLogic"; при условии, что я правильно вас понимаю.

Loduwijk 10.03.2017 01:19

Вы можете использовать PowerMockito для установки возвращаемых значений для частных полей и частных методов, которые вызываются / используются в частном методе, который вы хотите протестировать:

Например. Установка возвращаемого значения для частного метода:

MyClient classUnderTest = PowerMockito.spy(new MyClient());

//Set expected return value
PowerMockito.doReturn(20).when(classUnderTest, "myPrivateMethod", anyString(), anyInt());
//This is very important otherwise it will not work
classUnderTest.myPrivateMethod(); 

//Setting private field value as someValue:
Whitebox.setInternalState(classUnderTest, "privateField", someValue);

Затем, наконец, вы можете проверить, что ваш частный тестируемый метод возвращает правильное значение на основе установленных выше значений:

String msg = Whitebox.invokeMethod(obj, "privateMethodToBeTested", "param1");
Assert.assertEquals(privateMsg, msg);

Или же

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

//To get value of private field
MyClass obj = Whitebox.getInternalState(classUnderTest, "foo");
assertThat(obj, is(notNull(MyClass.class))); // or test value

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

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

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

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

Constantinos 29.10.2017 22:25

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

Miladinho 07.01.2019 22:52

Лучший и правильный законный способ протестировать частный метод Java из тестовой среды - это аннотация @VisibleForTesting над методом, поэтому тот же метод будет виден для тестовой среды, как и общедоступный метод.

В C++: перед включением заголовка класса с частной функцией, которую вы хотите протестировать

Используйте этот код:

#define private public
#define protected public

Ой. Я думаю, это работает нормально, но я бы предпочел использовать объявления друзей, а не возиться с #defines на таком базовом уровне.

Franz D. 05.07.2018 17:32

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

Matt Messersmith 15.08.2018 04:58

К вашему сведению, это больше не работает в VS2019, больше нельзя использовать макросы таким образом.

AndersK 25.02.2019 10:19

PowerMockito создан для этого. Использовать зависимость от maven

    <dependency>
        <groupId>org.powermock</groupId>
        <artifactId>powermock-core</artifactId>
        <version>2.0.7</version>
        <scope>test</scope>
    </dependency>

Тогда ты можешь сделать

import org.powermock.reflect.Whitebox;
...
MyClass sut = new MyClass();
SomeType rval = Whitebox.invokeMethod(sut, "myPrivateMethod", params, moreParams);

Моя команда и я используем Typemock, у которого есть API, позволяющий поддельные непубличные методы. Недавно они добавили возможность поддельные невидимые типы и использовать XUnit.

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

О том, как их использовать, вы можете найти множество статей в Интернете. Вот один, который мне особенно понравился:

Вы можете создать специальный общедоступный метод, чтобы проксировать частный метод для тестирования. Аннотации @TestOnly из коробки доступны при использовании IntelliJ. Обратной стороной является то, что если кто-то хочет использовать частный метод в общедоступном контексте, он может это сделать. Но он будет предупрежден по аннотации и названию метода. В IntelliJ при этом появится предупреждение.

import org.jetbrains.annotations.TestOnly

class MyClass {

    private void aPrivateMethod() {}

    @TestOnly
    public void aPrivateMethodForTest() {
        aPrivateMethod()
    }
}

Есть еще один подход к тестированию ваших частных методов. Если вы «включаете утверждение» в конфигурациях запуска, вы можете выполнить модульное тестирование своего метода внутри самого метода. Например;

assert ("Ercan".equals(person1.name));
assert (Person.count == 2);

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

  • тестировать частные внутренние состояния (поля) с помощью PowerMock или просто отражать без накладных расходов на введение другой независимости
  • для частных методов: на самом деле, голос за этот вопрос, а также огромное количество комментариев и ответов показывает, что это очень параллельная и противоречивая тема где нельзя было дать однозначный ответ на все обстоятельства. Я понимаю, что нужно тестировать только контракт, но мы также должны учитывать покрытие. Собственно, Сомневаюсь, что только контракты на тестирование на 100% сделают класс невосприимчивым к ошибкам.. Частные методы - это методы, которые обрабатывают данные в классе, в котором они определены, и поэтому не интересуют другие классы, поэтому мы не можем просто раскрыть их, чтобы сделать их тестируемыми. Я буду постарайтесь не проверять их, но когда нужно, просто сделай это и забудь все ответы здесь. Вы знаете свою ситуацию и ограничения лучше, чем кто-либо другой в Интернете. Когда у вас есть контроль над своим кодом, используйте его. Внимательно, но не задумываясь.

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

Во-первых, Powermock.Whitebox все еще можно использовать.

Кроме того, Mockito Whitebox был скрыт после v2 (последняя версия, которую я могу найти с Whitebox, - это testImplementation 'org.mockito:mockito-core:1.10.19'), и он всегда был частью пакета org.mockito.internal, который подвержен критическим изменениям в будущем (см. эта почта). Так что теперь я стараюсь не использовать его.

В проектах Gradle / Maven, если вы определяете частные методы или поля, нет других способов, кроме отражения, для получения доступа к ним, поэтому первая часть остается верной. Но, если вы измените видимость на «частный пакет», тесты, следующие той же структуре в пакете test, будут иметь к ним доступ.. Это еще одна важная причина, по которой нам рекомендуется создавать ту же иерархию в пакетах main и test. Итак, когда у вас есть контроль над производственным кодом, а также над тестами, удаление этого модификатора доступа private может быть лучшим вариантом для вас, потому что относительно он не оказывает большого влияния. И это делает возможным тестирование, а также шпионаж частным методом.

@Autowired
private SomeService service; // with a package private method "doSomething()"

@Test
void shouldReturnTrueDoSomething() {
    assertThat(doSomething(input), is(true)); // package private method testing
}

@Test
void shouldReturnTrueWhenServiceThrowsException() {
    SomeService spy = Mockito.spy(service); // spying real object
    doThrow(new AppException()).when(spy).doSomething(input); // spy package private method
    ...

}

Что касается внутренних полей, в Spring у вас есть ReflectionUtils.setField().

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

Android имеет аннотацию @VisibleForTesting из пакета android.support.annotation.

The @VisibleForTesting annotation indicates that an annotated method is more visible than normally necessary to make the method testable. This annotation has an optional otherwise argument that lets you designate what the visibility of the method should have been if not for the need to make it visible for testing. Lint uses the otherwise argument to enforce the intended visibility.

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

Например

package com.mypackage;

public class ClassA {

    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
    static void myMethod() {

    }
}

И когда вы вызываете ClassA.myMethod () в том же пакете (com.mypackage), вы увидите предупреждение.

Для C++ (начиная с C++ 11) добавление тестового класса в качестве друга отлично работает и не нарушает производственную инкапсуляцию.

Предположим, у нас есть класс Foo с некоторыми частными функциями, которые действительно требуют тестирования, и класс FooTest, который должен иметь доступ к закрытым членам Foo. Затем мы должны написать следующее:

// prod.h: some production code header

// forward declaration is enough
// we should not include testing headers into production code
class FooTest;

class Foo
{
  // that does not affect Foo's functionality
  // but now we have access to Foo's members from FooTest
  friend FooTest;
public:
  Foo();
private:
  bool veryComplicatedPrivateFuncThatReallyRequiresTesting();
}
// test.cpp: some test
#include <prod.h>

class FooTest
{
public:
  void complicatedFisture() {
    Foo foo;
    ASSERT_TRUE(foo.veryComplicatedPrivateFuncThatReallyRequiresTesting());
  }
}

int main(int /*argc*/, char* argv[])
{
  FooTest test;
  test.complicatedFixture();  // and it really works!
}

Существует также трюк с шаблоном, который позволяет получить доступ к закрытым членам (через косвенный указатель IIRC) или просто к -fno-access-control (который просто игнорирует ограничения private и protected).

Xeverous 26.08.2019 21:37

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

import com.google.common.base.Preconditions;

import org.springframework.test.util.ReflectionTestUtils;

/**
 * <p>
 * Invoker
 * </p>
 *
 * @author
 * @created Oct-10-2019
 */
public class Invoker {
    private Object target;
    private String methodName;
    private Object[] arguments;

    public <T> T invoke() {
        try {
            Preconditions.checkNotNull(target, "Target cannot be empty");
            Preconditions.checkNotNull(methodName, "MethodName cannot be empty");
            if (null == arguments) {
                return ReflectionTestUtils.invokeMethod(target, methodName);
            } else {
                return ReflectionTestUtils.invokeMethod(target, methodName, arguments);
            }
        } catch (Exception e) {
           throw e;
        }
    }

    public Invoker withTarget(Object target) {
        this.target = target;
        return this;
    }

    public Invoker withMethod(String methodName) {
        this.methodName = methodName;
        return this;
    }

    public Invoker withArguments(Object... args) {
        this.arguments = args;
        return this;
    }

}

Object privateMethodResponse = new Invoker()
  .withTarget(targetObject)
  .withMethod(PRIVATE_METHOD_NAME_TO_INVOKE)
  .withArguments(arg1, arg2, arg3)
  .invoke();
Assert.assertNotNutll(privateMethodResponse)

Если вы используете только Mockito:

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

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

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

Это позволяет имитировать эти объекты и вводить их обратно в объект тестирования. Вы также можете издеваться над их методами (используя когда / тогда).

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

Преимущества 1. покрытие кода 2. Возможность протестировать полный частный метод.

Недостатки 1. Область действия объекта. Если вы не хотите, чтобы объект был открыт для других методов в том же классе, это может быть не ваш путь. 2. Возможно, вам придется тестировать частный метод несколько раз, когда он вызывается из разных открытых методов и / или в одном и том же методе несколько раз.

Привет, используйте этот служебный класс, если вы находитесь на весна.

ReflectionTestUtils.invokeMethod(new ClassName(), "privateMethodName");

В вашем классе:

namespace my_namespace {
    #ifdef UNIT_TEST
        class test_class;
    #endif

    class my_class {
        public:
            #ifdef UNIT_TEST
                friend class test_class;
            #endif
        private:
            void fun() { cout << "I am private" << endl; }
    }
}

В вашем классе модульного теста:

#ifndef UNIT_TEST
    #define UNIT_TEST
#endif

#include "my_class.h"

class my_namespace::test_class {
    public:
        void fun() { my_obj.fun(); }
    private:
        my_class my_obj;
}

void my_unit_test() {
    test_class test_obj;
    test_obj.fun(); // here you accessed the private function ;)
}

Детализируйте мой образец с ломбоком, как показано ниже. частное поле, частный метод:

public static void main(String[] args) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
    Student student = new Student();

    Field privateFieldName = Student.class.getDeclaredField("name");
    privateFieldName.setAccessible(true);
    privateFieldName.set(student, "Naruto");

    Field privateFieldAge = Student.class.getDeclaredField("age");
    privateFieldAge.setAccessible(true);
    privateFieldAge.set(student, "28");

    System.out.println(student.toString());

    Method privateMethodGetInfo = Student.class.getDeclaredMethod("getInfo", String.class, String.class);
    privateMethodGetInfo.setAccessible(true);
    System.out.println(privateMethodGetInfo.invoke(student, "Sasuke", "29"));
}


@Setter
@Getter
@ToString
class Student {
  private String name;
  private String age;

  private String getInfo(String name, String age) {
    return name + "-" + age;
  }
}

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