Тестування приватного методу за допомогою mockito


104
публічний клас A {

    метод публічної недійсності (логічний b) {
          якщо (b == вірно)
               метод1 ();
          ще
               метод2 ();
    }

    приватний метод недійсності1 () {}
    private void method2 () {}
}
громадський клас TestA {

    @Test
    public void testMethod () {
      A a = макет (A.class);
      a.method (правда);
      // як протестувати як verify (a) .method1 ();
    }
}

Як перевірити приватний метод називається чи ні, і як протестувати приватний метод за допомогою mockito ???


Відповіді:


81

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


19
Я плутаю цю відповідь. Це насмішкувато, але назва перевіряє приватні методи
diyoda_

Я використовував Powermock для глузування з приватного методу, але як я можу перевірити приватний метод за допомогою Powermock. Де я можу передати деякий вхід і очікувати деякого виходу від методу, а потім перевірити вихід?
Ріто

Ви не можете. Ви знущаєтесь над вхідним виходом, ви не можете перевірити реальну функціональність.
Талха

131

Неможливо через макет. З їх вікі

Чому Mockito не знущається з приватних методів?

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

Він вимагає злому навантажувачів класів, що ніколи не є кулезахисним, і це змінює api (ви повинні використовувати спеціальний тестовий бігун, анотувати клас тощо).

Дуже легко обійтися - просто змінити видимість методу з приватного на захищений пакетом (або захищений).

Це вимагає від мене витратити час на його реалізацію та підтримку. І це не має сенсу з точки №2 та факту, що він вже реалізований в іншому інструменті (powermock).

Нарешті ... Знущання над приватними методами - це натяк на те, що в розумінні ОО є щось не так. В OO ви хочете співпрацювати об'єкти (або ролі), а не методи. Забудьте про пароль і процедурний код. Думайте в об’єктах.


1
Це твердження припускає це твердження:> Знущання над приватними методами - це натяк на те, що в розумінні ОО є щось не так. Якщо я тестую публічний метод, і він викликає приватні методи, я б хотів знущатися над поверненнями приватних методів. Виходячи з вищенаведеного припущення, відпадає необхідність навіть застосовувати приватні методи. Як це погане розуміння ОО?
яєчники

1
@eggmatters За словами Baeldung, "Прийоми глузування повинні застосовуватися до зовнішніх залежностей класу, а не до самого класу. Якщо глузування з приватних методів є важливим для тестування наших класів, це зазвичай вказує на поганий дизайн". Ось крута нитка про це softwareengineering.stackexchange.com/questions/100959/…
Джейсон Гліз

34

Ось невеликий приклад, як це зробити з пауермоком

public class Hello {
    private Hello obj;
    private Integer method1(Long id) {
        return id + 10;
    }
} 

Для тестування method1 використовуйте код:

Hello testObj = new Hello();
Integer result = Whitebox.invokeMethod(testObj, "method1", new Long(10L));

Для встановлення приватного об'єкта obj використовуйте це:

Hello testObj = new Hello();
Hello newObject = new Hello();
Whitebox.setInternalState(testObj, "obj", newObject);

Ваше посилання вказує лише на power mock repo @Mindaugas
Xavier

@Xavier правда. Ви можете використовувати його, якщо вам подобається у вашому проекті.
Mindaugas Jaraminas

1
Чудовий !!! так добре пояснено з цим простим прикладом майже все :) Тому що мета полягає лише в тестуванні коду, а не в тому, що передбачено всіма рамками :)
siddhusingh

Оновіть це. Whitebox більше не є частиною публічного API.
user447607

17

Подумайте про це з точки зору поведінки, а не з точки зору, які існують методи. Викликаний метод methodмає особливу поведінку, якщо bце правда. Він має іншу поведінку, якщо bнеправдивий. Це означає, що ви повинні написати два різні тести для method; по одному для кожного випадку. Тож замість того, щоб мати три методи, орієнтовані на метод (один для method, один для method1, один для method2, у вас є два тести, орієнтовані на поведінку.

Пов’язане з цим (я нещодавно запропонував це в іншій темі SO, і в результаті отримав назву чотири літерного слова, тому сміливо сприймайте це із зерном солі); Мені корисно вибирати назви тестів, які відображають поведінку, яку я тестую, а не назву методу. Так що не називають тести testMethod(), testMethod1(), testMethod2()і так далі. Мені подобаються імена на кшталт calculatedPriceIsBasePricePlusTax()або taxIsExcludedWhenExcludeIsTrue()які вказують, яку поведінку я тестую; то в рамках кожного методу випробування випробовуйте лише вказану поведінку. Більшість таких форм поведінки передбачає лише один виклик до публічного методу, але може включати багато викликів до приватних методів.

Сподіваюся, це допомагає.


13

Поки Mockito не надає такої можливості, ви можете досягти того ж результату, використовуючи Mockito + клас JUnit ReflectionUtils або клас Spring ReflectionTestUtils . Будь ласка , дивіться приклад нижче взяті з тут пояснити , як викликати приватний метод:

ReflectionTestUtils.invokeMethod(student, "saveOrUpdate", "From Unit test");

Повні приклади з ReflectionTestUtils та Mockito можна знайти в книзі Mockito for Spring


ReflectionTestUtils.invokeMethod (студент, "saveOrUpdate", "argument1", "argument2", "argument3"); В останньому аргументі invokeMethod використовується Vargs, який може приймати кілька аргументів, які потрібно передати приватному методу. це працює.
Тім

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

9

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

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

У наведеному вище прикладі два способи, які називаються "випадковим чином", можливо, фактично повинні бути поміщені у власний клас, протестовані та потім введені в клас вище.


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

2
Помітивши супертонського, я мав на увазі загальну справу. Я згоден, що у наведеному випадку це не повинно бути в окремому класі. (+1, однак, ваш коментар - це дуже вагомий момент, який ви робите для просування приватних членів)
Jaco Van Niekerk,

4
@supertonsky, мені не вдалося знайти задовільної відповіді на цю проблему. Є декілька причин, чому я можу використовувати приватних членів, і дуже часто вони не вказують на запах коду, і я б дуже корисний від їх тестування. Люди, здається, вирішують це, кажучи: "просто не роби цього".
LuddyPants

3
Вибачте, я вирішив скасувати заявку на основі "Якщо ви хочете" протестувати приватні методи, це може означати, що вам потрібно зняти проект ". Гаразд, досить чесно, але одна з причин тестування взагалі полягає в тому, що під час терміну, коли у вас немає часу на переосмислення дизайну, ви намагаєтесь безпечно реалізувати зміни, які потрібно внести до приватний метод. Чи в ідеальному світі приватний метод не потрібно міняти, оскільки дизайн був би ідеальним? Звичайно, але в ідеальному світі, але це суперечка, тому що в ідеальному світі, якому потрібні тести, все це просто працює. :)
Джон Локвуд

2
@John. Точка прийнята, ваш потік обґрунтований (+1). Дякую також за коментар - я погоджуюсь з вами щодо точки, яку ви висловлюєте. У таких випадках я бачу один із двох варіантів: або метод робиться пакетним-приватним, або захищеним, а одиничні тести записуються як зазвичай; або (а це язика в шкірі і погана практика), основний метод швидко пишеться, щоб переконатися, що він все-таки працює. Однак моя відповідь ґрунтувалася на сценарії написання НОВОГО коду, а не на рефакторингу, коли ви, можливо, не зможете підробити оригінальний дизайн.
Яко Ван Нікерк

6

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

//Service containing the mock method is injected with mockObjects

@InjectMocks
private ServiceContainingPrivateMethod serviceContainingPrivateMethod;

//Using reflection to change accessibility of the private method

Class<?>[] params = new Class<?>[]{PrivateMethodParameterOne.class, PrivateMethodParameterTwo.class};
    Method m = serviceContainingPrivateMethod .getClass().getDeclaredMethod("privateMethod", params);
    //making private method accessible
    m.setAccessible(true); 
    assertNotNull(m.invoke(serviceContainingPrivateMethod, privateMethodParameterOne, privateMethodParameterTwo).equals(null));

6
  1. Використовуючи рефлексію, приватні методи можна викликати з тестових класів. В цьому випадку,

    // Метод тестування буде таким ...

    public class TestA {
    
      @Test
        public void testMethod() {
    
        A a= new A();
        Method privateMethod = A.class.getDeclaredMethod("method1", null);
        privateMethod.setAccessible(true);
        // invoke the private method for test
        privateMethod.invoke(A, null);
    
        }
    }
  2. Якщо приватний метод викликає будь-який інший приватний метод, то нам потрібно шпигувати об’єкт і заглушити інший метод. Тестовий клас буде, як ...

    // Метод тестування буде таким ...

    public class TestA {
    
      @Test
        public void testMethod() {
    
        A a= new A();
        A spyA = spy(a);
        Method privateMethod = A.class.getDeclaredMethod("method1", null);
        privateMethod.setAccessible(true);
        doReturn("Test").when(spyA, "method2"); // if private method2 is returning string data
        // invoke the private method for test
        privateMethod.invoke(spyA , null);
    
        }
    }

** Підхід полягає у поєднанні рефлексії та шпигування об’єкта. ** method1 та ** method2 - приватні методи, а method1 викликає метод2.


4

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

Кілька можливих рішень (AFAIK):

  1. Знущаються над вашими приватними методами, але все ж ви не будете "фактично" тестувати свої методи.

  2. Перевірте стан об'єкта, який використовується в методі. НАЙБІЛЬШІ методи або виконують деяку обробку вхідних значень і повертають вихід, або змінюють стан об'єктів. Тестування об'єктів на бажаний стан також може бути використане.

    public class A{
    
    SomeClass classObj = null;
    
    public void publicMethod(){
       privateMethod();
    }
    
    private void privateMethod(){
         classObj = new SomeClass();
    }
    
    }

    [Тут ви можете перевірити приватний метод, перевіривши зміну стану classObj з null на null.]

  3. Трохи перефактуруйте свій код (Сподіваємось, це не застарілий код). Моя фундація написання методу полягає в тому, що завжди слід щось повертати (int / boolean). Повернене значення МОЖЕ ТА МОЖЕ НЕ використовуватись реалізацією, але воно ЗОВ'ЯЗКОВО буде використано тестом

    код.

    public class A
    { 
        public int method(boolean b)
        {
              int nReturn = 0;
              if (b == true)
                   nReturn = method1();
              else
                   nReturn = method2();
        }
    
        private int method1() {}
    
        private int method2() {}
    
    }

3

Насправді існує спосіб випробувати методи приватного члена Mockito. Скажімо, у вас такий клас:

public class A {
    private SomeOtherClass someOtherClass;
    A() {
        someOtherClass = new SomeOtherClass();
    }
    public void method(boolean b){
        if (b == true)
            someOtherClass.method1();
        else
            someOtherClass.method2();
    }

}

public class SomeOtherClass {
    public void method1() {}
    public void method2() {}
}

Якщо ви хочете, щоб тест a.methodвикликав метод SomeOtherClass, ви можете написати щось подібне нижче.

@Test
public void testPrivateMemberMethodCalled() {
    A a = new A();
    SomeOtherClass someOtherClass = Mockito.spy(new SomeOtherClass());
    ReflectionTestUtils.setField( a, "someOtherClass", someOtherClass);
    a.method( true );

    Mockito.verify( someOtherClass, Mockito.times( 1 ) ).method1();
}

ReflectionTestUtils.setField(); змусить приватного члена щось, що ви можете шпигувати.


2

Помістіть свій тест в той самий пакет, але в іншу вихідну папку (src / main / java vs. src / test / java) і зробіть ці методи пакетом приватними. Імотестичність важливіша, ніж конфіденційність.


6
Єдина законна причина - перевірити частину спадкової системи. Якщо ви почнете тестувати приватний / пакет-приватний метод, ви піддаєте внутрішній об єкт. Це, як правило, призводить до поганого коду, який можна відремонтувати. Здайте перевагу складу, щоб ви могли досягти перевірки, при всьому доброті об'єктно-орієнтованої системи.
Бріс

1
Домовились - це був би кращий спосіб. Однак якщо ви дійсно хочете перевірити приватні методи з mockito, це єдиний варіант (typesafe), який у вас є. Хоча моя відповідь була трохи поспішною, я мав би вказати на ризики, як ви та інші.
Роланд Шнайдер

Це мій бажаний спосіб. Немає нічого поганого в тому, щоб відкрити внутрішній об'єкт на рівні пакет-приватний; і тест одиниці - це тестування в білій коробці, ви повинні знати внутрішню для тестування.
Ендрю Фен

0

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

ArgumentCaptor<ByteArrayOutputStream> csvOutputCaptor = ArgumentCaptor.forClass(ByteArrayOutputStream.class);
//Do your thing..
verify(this.awsService).uploadFile(csvOutputCaptor.capture());
....
assertEquals(csvOutputCaptor.getValue().toString(), "blabla");
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.