Як ви використовуєте тестові приватні методи?


184

Я працюю над проектом java. Я новачок в одиничному тестуванні. Який найкращий спосіб поділити тест приватних методів у класах java?


1
Перевірте це питання в StackOverflow. Згадується і обговорюється пара прийомів. Який найкращий спосіб одиничного тестування приватних методів?
Хірон

34
Моя думка завжди була такою, що приватні методи не потребують тестування, як ви повинні тестувати те, що є в наявності. Публічний метод. Якщо ви не можете порушити загальнодоступний метод, чи справді має значення, чим займаються приватні методи?
Ріг

1
Як державні, так і приватні методи повинні бути перевірені. Отже, тест-драйвер, як правило, повинен бути в класі, який він тестує. як це .
переобмін

2
@Rig - +1 - ви маєте змогу викликати всю необхідну поведінку приватного методу з ваших публічних методів - якщо ви не можете, то функціонал ніколи не може бути використаний, тому немає сенсу перевіряти його.
Джеймс Андерсон

Використовуйте @Jailbreakз фреймворку Manifold для прямого доступу до приватних методів. Таким чином ваш тестовий код залишається безпечним і читабельним. Перш за все, жодних компромісів дизайну, жодних методів і полів для перекриття заради тестів.
Скотт

Відповіді:


239

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

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


39
+1 мільярд. Якщо приватний метод ніколи не викликається, не тестуйте його, видаліть його!
Стівен А. Лоу

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

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

38
@quant_dev: Іноді клас потрібно переробляти на кілька інших класів. Ваші спільні дані також можна перенести в окремий клас. Скажімо, "контекстний" клас. Тоді ваші два нові класи можуть посилатися на контекстний клас для їх спільних даних. Це може здатися необґрунтованим, але якщо ваші приватні методи досить складні, щоб потребувати індивідуального тестування, це запах коду, який вказує, що ваш об’єктний графік повинен стати трохи більш детальним.
Філ

43
Ця відповідь НЕ відповідає на запитання. Питання полягає в тому, як слід перевіряти приватні методи, а не чи слід їх перевіряти. Чи має бути приватний метод перевіреним, цікаве питання та гідне обговорення, але не тут . Відповідна відповідь - це додати коментар до запитання, вказуючи, що тестування приватних методів може бути не надто хорошою ідеєю та давати посилання на окреме запитання, яке заглибиться у цю проблему глибше.
TallGuy

118

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

З іншого боку, іноді розподілення деталей про реалізацію в окремі класи призводить до класів зі складними інтерфейсами, великою кількістю даних, що передаються між старим та новим класом, або до дизайну, який може виглядати добре з точки зору OOP, але не відповідати інтуїціям, що надходять із проблемної області (наприклад, розділення моделі ціноутворення на дві частини, щоб уникнути тестування приватних методів не дуже інтуїтивно зрозуміло і може призвести до проблем згодом при підтримці / розширенні коду). Ви не хочете мати "побратими", які завжди змінюються разом.

Якщо ви зіткнулися з вибором між інкапсуляцією та тестабельністю, я б краще пішов на друге. Важливіше мати правильний код (тобто виробляти правильний вихід), ніж приємний дизайн OOP, який не працює належним чином, тому що він не був перевірений належним чином. У Java ви можете просто надати методу "за замовчуванням" доступ і помістити тестовий пристрій в той же пакет. Блокові тести - це просто частина пакету, який ви розробляєте, і нормально мати залежність між тестами та кодом, який тестується. Це означає, що коли ви змінюєте реалізацію, можливо, вам доведеться змінити свої тести, але це нормально - кожна зміна реалізації вимагає повторного тестування коду, і якщо для цього потрібно змінити тести, ви просто зробите це.

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


9
+1 Цікавий момент. Зрештою, мова йде про ремонтопридатність, а не дотримання "правил" хорошого ООП.
Філ

9
+1 Я повністю погоджуюся з доказовістю щодо інкапсуляції.
Річард

31

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

Моя краща методика - зробити приватний пакет методів приватним, що дозволить отримати доступ до тестової одиниці в тому ж пакеті, але він все одно буде інкапсульований з усіх інших кодів. Це дасть перевагу тестуванню логіки приватного методу безпосередньо, замість того, щоб покладатися на тест публічного методу, щоб охопити всі частини (можливо) складної логіки.

Якщо це в поєднанні з анотацією @VisibleForTesting в бібліотеці Google Guava, ви чітко позначаєте цей пакет приватним методом як видимий лише для тестування, і він не повинен викликати жодні інші класи.

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

Приватний метод перед тестуванням:

private int add(int a, int b){
    return a + b;
}

Приватний метод упаковки готовий до тестування:

@VisibleForTesting
int add(int a, int b){
    return a + b;
}

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


14

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

MyObject obj = new MyObject();
Method privateMethod = MyObject.class.getDeclaredMethod("getFoo", null);
privateMethod.setAccessible(true);
String returnValue = (String) privateMethod.invoke(obj, null);
System.out.println("returnValue = " + returnValue);

Перевірте http://docs.oracle.com/javase/tutorial/reflect/ або Java API http://docs.oracle.com/javase/7/docs/api/java/lang/reflect/package-summary. html для отримання додаткової інформації.

Як @kij цитував у своїй відповіді, бувають випадки, коли просте рішення з використанням рефлексії справді добре перевірити приватний метод.


9

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

Якщо ви використовуєте Java, ви можете використовувати jmockit, який надає Deencapsulation.invoke, щоб викликати будь-який приватний метод класу, який тестується. Він використовує роздуми, щоб викликати це в кінцевому підсумку, але забезпечує гарну обгортку навколо нього. ( https://code.google.com/p/jmockit/ )


як це відповідає на поставлене запитання?
гнат

@gnat, Питання полягало в тому, що найкращий спосіб одиничного тестування приватних методів у класах java? І мій перший пункт відповідає на питання.
agrawalankur

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

jmockit перемістився, і стара офіційна сторінка вказує на статтю про "Синтетичну сечу та штучний пі". До речі, це все ще пов’язано з глузуванням з матеріалів, але тут не актуально :) Нова офіційна сторінка: jmockit.github.io
Гійом

8

Перш за все, як запропонували інші автори: подумайте, чи справді вам потрібно перевірити приватний метод. І якщо так, ...

У .NET ви можете перетворити його у метод "Внутрішній" та зробити пакет " InternalVisible " для вашого тестового проекту.

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

Дякую.


7

Я зазвичай роблю такі методи захищеними. Скажімо, ваш клас знаходиться в:

src/main/java/you/Class.java

Ви можете створити тестовий клас як:

src/test/java/you/TestClass.java

Тепер у вас є доступ до захищених методів і ви можете їх перевірити (JUnit або TestNG насправді не має значення), але ви зберігаєте ці методи від тих, хто вам не хотів.

Зауважте, що це очікує дерево джерела в стилі Maven.


3

Якщо вам справді потрібно перевірити приватний метод, я маю на увазі Java, ви можете використовувати fest assertта / або fest reflect. Тут використовується рефлексія.

Імпортуйте бібліотеку з maven (наведені версії не є останніми, на мою думку) або імпортуйте їх безпосередньо у ваш клас:

<dependency>
     <groupId>org.easytesting</groupId>
     <artifactId>fest-assert</artifactId>
     <version>1.4</version>
    </dependency>

    <dependency>
     <groupId>org.easytesting</groupId>
     <artifactId>fest-reflect</artifactId>
    <version>1.2</version>
</dependency>

Наприклад, якщо у вас є клас під назвою "MyClass" з приватним методом під назвою "myPrivateMethod", який приймає для параметра String оновлення свого значення на "це класне тестування!", Ви можете зробити наступний тест на юніт:

import static org.fest.reflect.core.Reflection.method;
...

MyClass objectToTest;

@Before
public void setUp(){
   objectToTest = new MyClass();
}

@Test
public void testPrivateMethod(){
   // GIVEN
   String myOriginalString = "toto";
   // WHEN
   method("myPrivateMethod").withParameterTypes(String.class).in(objectToTest).invoke(myOriginalString);
   // THEN
   Assert.assertEquals("this is cool testing !", myOriginalString);
}

Ця бібліотека також дозволяє замінити будь-які властивості біна (незалежно від того, вони є приватними і жодні налаштування не записуються) макетом, і використовувати це з Mockito або будь-яким іншим макетним фреймом дуже здорово. Єдине, що ви повинні знати на даний момент (не знаєте, чи буде це краще в наступних версіях) - це ім'я цільового поля / методу, яким ви хочете маніпулювати, та його підпис.


2
Хоча рефлексія дозволить вам протестувати приватні методи, це не добре для виявлення їх використання. Якщо ви зміните ім'я приватного методу, це порушить ваш тест.
Річард

1
Тест зламається так, але з моєї точки зору це незначна проблема. Звичайно, я повністю погоджуюся з вашим поясненням нижче щодо видимості пакету (з класами тестів в окремих папках, але з тими ж пакетами), але іноді ви цього не робите дійсно є вибір. Наприклад, якщо у вас дійсно коротка місія на підприємстві, яке не застосовує «хороших» методів (тестові класи не в одному пакеті і т. Д.), Якщо у вас немає часу на рефакторинг існуючого коду, над яким ви працюєте. / тестування, це все-таки хороша альтернатива.
kij

2

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

public class classUnderTest
{
   //this would normally be private
   protected void methodToTest()
   {
     //some code here
   }
}

Будь-який клас, який не успадковує безпосередньо клас classUnderTest, не має уявлення про те, що methodToTest навіть існує. У своєму тестовому коді я можу створити спеціальний клас тестування, який розширює та забезпечує доступ до цього методу ...

class TestingClass : classUnderTest
{
   public void methodToTest()
   {
     //this returns the method i would like to test
     return base.methodToTest();
   }
}

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


4
protectedє частиною загальнодоступного API та має ті самі обмеження (їх ніколи не можна змінювати, потрібно документувати, ...). В C # ви можете використовувати internalразом із InternalsVisibleTo.
CodesInChaos

1

Ви можете легко протестувати приватні методи, якщо помістити свої одиничні тести у внутрішній клас класу, який ви тестуєте. Використовуючи TestNG, ваші одиничні тести повинні бути загальнодоступними статичними внутрішніми класами, в яких позначається @Test, наприклад:

@Test
public static class UserEditorTest {
    public void test_private_method() {
       assertEquals(new UserEditor().somePrivateMethod(), "success");
    }
}

Оскільки це внутрішній клас, приватний метод можна назвати.

Мої тести виконуються з Maven, і вони автоматично знаходять ці тестові випадки. Якщо ви просто хочете перевірити один клас, ви можете це зробити

$ mvn test -Dtest=*UserEditorTest

Джерело: http://www.ninthavenue.com.au/how-to-unit-test-private-methods


4
Ви пропонуєте включити тестовий код (та додаткові внутрішні класи) у фактичний код розгорнутого пакету?

1
Тестовий клас - це внутрішній клас класу, який ви хочете протестувати. Якщо ви не хочете, щоб внутрішні класи були розгорнуті, ви можете просто видалити файли * Test.class зі свого пакету.
Роджер Кіз

1
Мені не подобається цей варіант, але для багатьох це, мабуть, правильне рішення, не вартий скоромов.
Білл К

@RogerKeays: Як технічно ви робите розгортання, як видалити такі "тестові внутрішні класи"? Будь ласка, не кажіть, що ви ріжете їх вручну.
gyorgyabraham

Я не знімаю їх. Так. Я розгортаю код, який ніколи не виконується під час виконання. Це дійсно так погано.
Роджер Кіз
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.