Як перевірити приватну функцію або клас, який має приватні методи, поля чи внутрішні класи?


2726

Як я можу перевірити (використовуючи xUnit) клас, який має внутрішні приватні методи, поля чи вкладені класи? Або функція, яка стає приватною, маючи внутрішню зв'язок ( staticв C / C ++) або знаходиться в приватному ( анонімному ) просторі імен?

Дуже погано міняти модифікатор доступу на метод або функцію просто, щоб мати можливість запустити тест.


257
Найкращий спосіб перевірити приватний метод - це не тестувати його безпосередньо
Surya


383
Я не погоджуюсь. Довгий або важкий для розуміння метод (публічний) повинен бути відновлений. Нерозумно було б не перевіряти малі (приватні) методи, які ви отримуєте замість лише публічних.
Майкл Піефель

181
Не випробовувати жодних методів лише тому, що його видимість дурна. Навіть тест одиниці повинен містити найменший фрагмент коду, і якщо ви протестуєте лише загальнодоступні методи, ви ніколи не будете впевнені, де виникає помилка - цей метод чи якийсь інший.
Даїній

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

Відповіді:


1640

Оновлення:

Десь через 10 років, можливо, найкращий спосіб перевірити приватний метод або будь-який недоступний член - це через @Jailbreakсистему Manifold .

@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
Приклад коду для мене не працював, але це зробило тиги зрозумілішими: java2s.com/Tutorial/Java/0125__Reflection/…
Роб

35
Набагато краще, ніж використовувати рефлексію безпосередньо, було б використовувати для нього якусь бібліотеку, наприклад Powermock .
Майкл Пієфель

25
Ось чому тест-керований дизайн корисний. Це допомагає вам зрозуміти, що потрібно піддавати впливу, щоб перевірити поведінку.
Thorbjørn Ravn Andersen

621

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

  1. Приватний метод - мертвий код
  2. Поруч із класом, який ви протестуєте, є дизайнерський запах
  3. Метод, який ви намагаєтеся перевірити, не повинен бути приватним

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

30
@grinch Spot on. Єдине, що ви могли б отримати від тестування приватних методів - це налагодження інформації, і саме для цього налагоджувачі. Якщо ваші тести договору класу мають повне покриття, то ви маєте всю необхідну інформацію. Приватні методи - це деталь реалізації . Якщо ви перевірите їх, вам доведеться міняти свої тести щоразу, коли ваша зміна змінюється, навіть якщо контракт цього не робить. У повному життєвому циклі програмного забезпечення це, швидше за все, коштуватиме набагато більше, ніж користь, яку вона дає.
Ерік Мадсен

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
@Eponymous Я трохи розірваний на цьому. В мені об'єктно орієнтований пурист сказав, що вам слід використовувати об'єктно-орієнтований підхід, а не статичні приватні методи, що дозволяють протестувати за допомогою публічних методів екземплярів. Але знову ж таки, в науковій рамці пам'ять, що, напевно, є проблемою, яка, на мою думку, є аргументом статичного підходу.
Ерік Мадсен

323

Коли у мене в класі є достатньо складні приватні методи, я відчуваю необхідність безпосередньо перевірити приватні методи, це запах коду: мій клас занадто складний.

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

Новий клас розкриває ці методи як "загальнодоступні", тому вони доступні для одиничного тестування. Нові та старі класи тепер простіші, ніж оригінальний клас, що для мене чудово (мені потрібно просто робити речі, інакше я втрачаюсь!).

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


24
Питання полягало в тому, як перевірити приватні методи. Ви говорите, що ви зробили б для цього новий клас (і додали б набагато складніше), а після пропонуєте не створювати новий клас. Тож як перевірити приватні методи?
Дайній

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

13
Але ви погоджуєтесь, що хороший OOD викриє (оприлюднить) лише методи, необхідні для правильного роботи класу / об'єкта? всі інші повинні бути приватними / protectec. Так що в деяких приватних методах буде певна логіка, і IMO тестування цих методів лише покращить якість програмного забезпечення. Звичайно, я згоден, що якщо якийсь фрагмент коду є складним, його слід розділити на окремі методи / клас.
Даїній

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

5
Це саме те , як використовувати відгуки від ваших тестів для управління та вдосконалення дизайну! Слухайте ваші тести. якщо їх важко написати, це говорить вам про щось важливе про ваш код!
pnschofield

289

Я раніше використовував роздуми, щоб зробити це для Java, і, на мою думку, це була велика помилка.

Строго кажучи, ви не повинні писати одиничні тести, які безпосередньо перевіряють приватні методи. Що ви повинні тестувати, це державний контракт, який у класу є з іншими об'єктами; ніколи не слід безпосередньо перевіряти внутрішні об'єкти. Якщо інший розробник хоче внести невелику внутрішню зміну класу, що не впливає на державний контракт класів, він / вона повинен змінити ваш тест на основі відображення, щоб переконатися, що він працює. Якщо ви робите це неодноразово протягом проекту, одиничні тести потім перестають бути корисним вимірюванням стану коду і починають перешкоджати розвитку та роздратувати команду розробників.

Я рекомендую замість цього використовувати інструмент покриття коду, такий як Cobertura, щоб гарантувати, що написані вами одиничні тести забезпечують гідне покриття коду приватними методами. Таким чином, ви опосередковано перевіряєте, чим займаються приватні методи, і підтримуєте більш високий рівень спритності.


76
+1 до цього. На мою думку, це найкраща відповідь на питання. Тестуючи приватні методи, ви тестуєте реалізацію. Це перемагає мету одиничного тестування, яке полягає у випробуванні входів / виходів договору класу. Тест повинен лише знати достатньо про реалізацію, щоб знущатися над методами, які вона вимагає від її залежностей. Нічого більше. Якщо ви не можете змінити свою реалізацію без необхідності змінити тест - шанси на те, що ваша стратегія тестування погана.
Колін М

10
@Colin M Хоча це не те, про що він питає;) Нехай він вирішить це, ви не знаєте проекту.
Аарон Маркус

2
Не дуже правда. Для тестування невеликої частини приватного методу через публічний метод його використання може знадобитися багато зусиль. Загальнодоступний метод може зажадати значних налаштувань, перш ніж ви перейдете до лінії, яка викликає ваш приватний метод
ACV

1
Я згоден. Вам слід перевірити, чи виконується контракт. Не варто тестувати, як це робиться. Якщо контакт здійснюється без досягнення 100% покриття коду (приватними методами), це може бути мертвим або марним кодом.
wuppi

207

З цієї статті: Тестування приватних методів за допомогою JUnit та SuiteRunner (Bill Venners) у вас в основному є 4 варіанти:

  1. Не перевіряйте приватні методи.
  2. Надати доступ пакету методів.
  3. Використовуйте вкладений тестовий клас.
  4. Використовуйте рефлексію.

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

@Pacerier Що ти маєш на увазі?
Джиммі Т.

1
5-й варіант, як зазначено вище, - це тестування публічного методу, який викликає приватний метод? Правильно?
користувач2441441

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

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

127

Зазвичай блок-тест призначений для здійснення публічного інтерфейсу класу чи підрозділу. Тому приватні методи - це детальна інформація про реалізацію, яку ви не очікували б явно перевірити.


16
Це найкраща відповідь ІМО, або, як це зазвичай прийнято говорити, тестова поведінка, а не методи. Тестування блоків не є заміною для метрик вихідного коду, інструментів аналізу статичного коду та оглядів коду. Якщо приватні методи настільки складні, що для них потрібні окремі тести, то, ймовірно, його потрібно буде відновити, а не більше тестів, що піддаються цьому.
Ден Хейнес

14
тому не пишіть приватні методи, просто створіть 10 інших невеликих класів?
бритва

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

66

Лише два приклади, де я хотів би перевірити приватний метод:

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

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

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


5
Хоча ви ніколи не повинні оприлюднювати метод лише для тестування, privateце не єдина альтернатива. Немає модифікатора доступу package privateі означає, що ви можете перевірити його, доки ваш тестовий пристрій живе в одному пакеті.
ngreen

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

1
@ngreen true thx - я був ледачий зі словом "публічний". Я оновив відповідь, щоб включити загальнодоступні, захищені, за замовчуванням (та згадати про роздуми). Я намагався переконатись у тому, що є вагомі причини, щоб якийсь код був секретним, однак це не повинно заважати нам його перевірити.
Річард Ле Месюр'є

2
Дякуємо за те, що надали реальні приклади для всіх. Більшість відповідей хороші, але з теоретичної точки зору. У реальному світі нам потрібно приховати виконання контракту, і нам ще потрібно перевірити його. Читаючи все це, я думаю, можливо, це повинен бути абсолютно новий рівень тестування, з іншою назвою
З. Хулла

1
Ще один приклад - парнінг 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 ), а не рекомендований проект PrivateAccessor Source Forge.
skia.heliou

2
І будьте впевнені, що, використовуючи параметр a Classв якості свого першого параметра в цих методах, ви отримуєте доступ до лише staticчленів.
skia.heliou

31

У Spring Framework ви можете протестувати приватні методи за допомогою цього методу:

ReflectionTestUtils.invokeMethod()

Наприклад:

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

Найчистіше і найкоротше рішення поки що, але тільки якщо ви використовуєте Spring.
Денис Ніколаєнко

28

Спробувавши рішення Джема Катікаса, використовуючи роздуми для 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

Я бачив класи, де єдиний публічний метод є основним [], і вони спливають GUI плюс підключають базу даних та пару веб-серверів у всьому світі. Легко сказати «не
тестуй

26

Приватні методи споживаються державними. Інакше вони мертвий код. Ось чому ви протестуєте публічний метод, стверджуючи очікувані результати публічного методу, а отже, і приватні методи, які він споживає.

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

Вони також можуть бути налагоджені за допомогою тестово-керованої розробки, налагоджуючи тести вашого блоку, поки всі ваші твердження не будуть виконані.

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


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

24

Якщо використовується Spring, ReflectionTestUtils надає деякі зручні інструменти, які допомагають тут з мінімальними зусиллями. Наприклад, налаштувати макет на приватного члена, не змушуючи його додавати небажаний публічний сетер:

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

24

Якщо ви намагаєтеся перевірити існуючий код, який ви не хочете або не можете змінити, роздуми - це хороший вибір.

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

Якщо ви хочете перевірити складні умови помилок, що випливають із допоміжного методу, ви можете піти на крок далі. Витягніть інтерфейс з helper-класу, додайте до початкового класу загальнодоступний getter та сетер, щоб ввести клас helper (використовується через його інтерфейс), а потім введіть макетну версію helper-класу в початковий клас, щоб перевірити, як оригінальний клас відповідає на винятки від помічника. Цей підхід також корисний, якщо ви хочете протестувати оригінальний клас, не перевіряючи також допоміжний клас.


19

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

Тому не перевіряйте приватні методи.


4
Тест одиниці та код src - пара. Якщо ви змінюєте src, можливо, вам доведеться змінити тест одиниці. Такий сенс тесту з джуніту. Вони повинні гарантувати, що все працює, як раніше. і це добре, якщо вони зламаються, якщо змінити код.
AlexWien

1
Не погоджуйтесь, що тест одиниці повинен змінюватися, якщо зміниться код. Якщо вам наказано додати функціональність до класу без зміни існуючої функціональності класу, то існуючі одиничні тести повинні пройти навіть після того, як ви змінили свій код. Як згадує Пітер, одиничні тести повинні перевірити інтерфейс, а не те, як це робиться внутрішньо. У тестовому керуванні тестовими модулями розробляються тести до створення коду, щоб зосередити увагу на інтерфейсі класу, а не на тому, як воно вирішено внутрішньо.
Харальд Коппулз

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складність до того, що це може спричинити більше проблем ... і, отже, вимагати додаткового тестування ... що, якщо ви реалізували таким чином, що може спричинити більше проблем .. . (це не нескінченні петлі; кожна ітерація, ймовірно, менша за попередню, але все ще дратує)
Loduwijk

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 ) } }коли-небудь робить простішим у обслуговуванні. Бувають випадки, коли подібні ін'єкції мають сенс, але я не вважаю ваш приклад "легшим у обслуговуванні"
Loduwijk

12

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

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

Якщо все вищесказане не відповідає вашим вимогам, використовуйте спосіб відображення для доступу до приватного методу.


Ви змішуєте "захищений" із "дружнім". До захищеного методу може бути доступний лише клас, об'єкти якого можна віднести до цільового класу (тобто підкласи).
Sayo Oladeji

1
Насправді в Java немає "Дружніх", термін - "Пакет", і це вказується на відсутність приватного / публічного / захищеного модифікатора. Я би щойно виправив цю відповідь, але є дійсно хороший, який вже говорить про це, тому я просто рекомендую її видалити.
Білл К

Я майже не прихильнився, думаючи весь час: "Ця відповідь явно неправильна". поки я не дійшов до останнього речення, яке суперечить решті відповіді. Останнє речення мало бути першим.
Loduwijk

11

Як багато з них було запропоновано, хороший спосіб - протестувати їх через свої загальнодоступні інтерфейси.

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


Не слід побічно тестувати! Не тільки торкатися через покриття; перевірити, чи очікується результат!
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;- Picklock

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.