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


2729

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

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


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


383
Я не согласен. (Публичный) метод, который является длинным или трудным для понимания, должен быть реорганизован. Было бы глупо не проверять маленькие (приватные) методы, которые вы получаете вместо общедоступных.
Майкл Пифель

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

126
Вам нужно проверить функциональность класса , а не его реализацию . Хотите проверить частные методы? Протестируйте публичные методы, которые их вызывают. Если функциональность, предлагаемая классом, тщательно протестирована, его внутренняя часть продемонстрировала правильность и надежность; Вам не нужно проверять внутренние условия. Тесты должны выдерживать отделение от тестируемых классов.
Calimar41

Ответы:


1640

Обновить:

Около 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)Требуется тренироваться с рядовыми.


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

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

20
Пример кода не работает для меня, но это сделало Thigs более понятным: java2s.com/Tutorial/Java/0125__Reflection/…
Роб

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

25
Вот почему Test Driven Design полезен. Это поможет вам выяснить, что нужно подвергать воздействию, чтобы проверить поведение.
Турбьерн Равн Андерсен

621

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

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

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

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

9
@ AlexWien ты прав. Охват был не правильным термином. То, что я должен был сказать, было «если ваши тесты контракта класса охватывают все значимые входы и все значимые состояния». Это, как вы правильно заметили, может оказаться невозможным для тестирования через открытый интерфейс к коду или косвенно, как вы на него ссылаетесь. Если это относится к некоторому коду, который вы тестируете, я склонен подумать либо: (1) контракт слишком щедрый (например, слишком много параметров в методе), либо (2) контракт слишком расплывчатый (например, поведение метода) сильно зависит от класса государства). Я бы рассмотрел оба дизайна запахов.
Эрик Мэдсен

16
Практически все частные методы не должны подвергаться непосредственному тестированию (для снижения затрат на обслуживание). Исключение: научные методы могут иметь такие формы, как f (a (b (c (x), d (y))), a (e (x, z)), b (f (x, y, z), z)) где a, b, c, d, e и f - ужасно сложные выражения, но в остальном бесполезны вне этой единственной формулы. Некоторые функции (например, MD5) трудно инвертировать, поэтому может быть сложно (NP-Hard) выбрать параметры, которые будут полностью покрывать пространство поведения. Проще проверить a, b, c, d, e, f независимо. Делая их публичными или частно-ложными, они могут быть использованы повторно. Решение: сделайте их приватными, но протестируйте их.
одноименный

4
@ Одноименный, я немного разорван этим. Во мне объектно-ориентированный пурист сказал бы, что вы должны использовать объектно-ориентированный подход, а не статические приватные методы, что позволяет вам тестировать через общедоступные методы экземпляра. Но опять же, в научных рамках память, скорее всего, будет проблемой, которая, как я полагаю, доказывает статический подход.
Эрик Мэдсен

323

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

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

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

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


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

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

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

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

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

289

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

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

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


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

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

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

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

207

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

  1. Не проверяйте частные методы.
  2. Предоставьте доступ к пакету методов.
  3. Используйте вложенный тестовый класс.
  4. Используйте отражение.

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

@Pacerier Что ты имеешь в виду?
Джимми Т.

1
Пятый вариант, как упомянуто выше, тестирует открытый метод, который вызывает закрытый метод? Правильный?
user2441441

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

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

127

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


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

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

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

67

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

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

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

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


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

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

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

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

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

59

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


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

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

39

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

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

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


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

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

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

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

34

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

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

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

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

31

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

ReflectionTestUtils.invokeMethod()

Например:

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

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

28

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


26

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

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

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


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

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

26

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

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

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

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


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

24

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

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

24

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

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

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


19

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

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


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

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

16

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

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

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

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

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


16

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


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

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

14

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


14

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


13

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

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

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

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

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

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


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

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

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

1
Метод тестирования не увеличивает сложность. Это просто ваш класс, который плохо написан, настолько плохо, что не может быть проверен.
GROX13

Труднее поддерживать, потому что класс сложнее. 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

12

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

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

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


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

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

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

11

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

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


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

11

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

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

10

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

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

import org.powermock.reflect.Whitebox;

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

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

10

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


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

9

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

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

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


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

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

8

Недавно я столкнулся с этой проблемой и написал небольшой инструмент под названием Picklock , который позволяет избежать проблем явного использования 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)- Пиклок

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;- Пиклок

interface Accessible {
  void setAmount(BigInteger amount);
}

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

7

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

<dependency>
    <groupId>org.powermock</groupId>
    <artifactId>powermock-mockito-release-full</artifactId>
    <version>1.6.4</version>
</dependency>

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

import org.powermock.reflect.Whitebox;
...
MyClass sut = new MyClass();
SomeType rval = Whitebox.invokeMethod(sut, "myPrivateMethod", params, moreParams);
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.