Тестирование частного метода с использованием mockito


104
public class A {

    public void method (boolean b) {
          если (b == истина)
               method1 ();
          еще
               method2 ();
    }

    private void method1 () {}
    private void method2 () {}
}
public class TestA {

    @Тест
    public void testMethod () {
      A a = mock (класс A.)
      a.method (истина);
      // как тестировать как verify (a) .method1 ();
    }
}

Как проверить вызывается частный метод или нет, и как протестировать частный метод с помощью mockito ???


Ответы:


81

Вы не можете сделать это с Mockito, но вы можете использовать Powermock для расширения Mockito и имитации частных методов. Powermock поддерживает Mockito. Вот вам пример.


19
Меня смущает этот ответ. Это насмешка, но в названии тестируются частные методы
diyoda_

Я использовал Powermock для имитации частного метода, но как я могу протестировать частный метод с помощью Powermock. Где я могу передать какой-то ввод и ожидать какой-то результат от метода, а затем проверить результат?
Рито

Вы не можете имитировать ввод-вывод, вы не можете проверить реальную функциональность.
Talha

131

Невозможно через mockito. Из их вики

Почему Mockito не издевается над частными методами?

Во-первых, мы не догадываемся о насмешках над частными методами. Нас просто не волнуют частные методы, потому что с точки зрения тестирования частных методов не существует. Вот пара причин, по которым Mockito не издевается над частными методами:

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

Обойти это очень просто - просто измените видимость метода с частного на защищенный пакетом (или защищенный).

Это требует от меня времени на его внедрение и поддержку. И это не имеет смысла, учитывая пункт №2 и тот факт, что он уже реализован в другом инструменте (powermock).

Наконец ... Мокинг частных методов - это намек на то, что что-то не так с объектно-ориентированным пониманием. В объектно-ориентированном стиле вы хотите, чтобы взаимодействовали объекты (или роли), а не методы. Забудьте о паскале и процедурном коде. Думайте объектами.


2
Это утверждение делает фатальное предположение:> Имитация частных методов - это намек на то, что что-то не так с объектно-ориентированным пониманием. Если я тестирую общедоступный метод и он вызывает частные методы, я бы хотел имитировать возвращение частного метода. Следуя приведенному выше предположению, отпадает необходимость даже в реализации частных методов. Как такое плохое понимание объектно-ориентированного программирования?
Яичницы

1
@eggmatters По словам Бэлдунга, «методы имитации должны применяться к внешним зависимостям класса, а не к самому классу. Если имитация частных методов необходима для тестирования наших классов, это обычно указывает на плохой дизайн». Вот классная ветка об этом softwareengineering.stackexchange.com/questions/100959/…
Джейсон Глез

35

Вот небольшой пример, как это сделать с помощью powermock

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

Для тестирования метода 1 используйте код:

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 @Mindaugas
Xavier

@Xavier правда. Вы можете использовать его, если хотите, в своем проекте.
Миндаугас Яраминас

1
Чудесно !!! так хорошо объяснено на этом простом примере почти все :) Потому что цель - просто протестировать код, а не то, что предоставляет весь фреймворк :)
siddhusingh

Пожалуйста, обновите это. Whitebox больше не является частью общедоступного API.
user447607 01

17

Подумайте об этом с точки зрения поведения, а не с точки зрения имеющихся методов. Вызываемый метод methodимеет особое поведение, если bистинно. Если bэто false, он ведет себя иначе . Это означает, что вам следует написать два разных теста для method; по одному на каждый случай. Таким образом, вместо трех тестов, ориентированных на метод (один для method, один для method1, один для method2, у вас есть два теста, ориентированных на поведение.

В связи с этим (я недавно предложил это в другом треде SO, и в результате меня назвали словом из четырех букв, так что не стесняйтесь относиться к этому с недоверием); Я считаю полезным выбирать имена тестов, которые отражают поведение, которое я тестирую, а не имя метода. Так что не называют тесты testMethod(), testMethod1(), testMethod2()и так далее. Мне нравятся такие имена, как calculatedPriceIsBasePricePlusTax()или taxIsExcludedWhenExcludeIsTrue()которые указывают, какое поведение я тестирую; затем в каждом методе тестирования проверьте только указанное поведение. В большинстве случаев такое поведение будет включать только один вызов общедоступного метода, но может включать много вызовов частных методов.

Надеюсь это поможет.


14

Хотя Mockito не предоставляет такой возможности, вы можете добиться того же результата, используя Mockito + класс JUnit ReflectionUtils или класс Spring ReflectionTestUtils . См. Пример ниже, взятый отсюда, объясняющий, как вызвать частный метод:

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

Полные примеры с ReflectionTestUtils и Mockito можно найти в книге Mockito for Spring.


ReflectionTestUtils.invokeMethod (студент, «saveOrUpdate», «аргумент1», «аргумент2», «аргумент3»); Последний аргумент invokeMethod использует Vargs, который может принимать несколько аргументов, которые необходимо передать частному методу. оно работает.
Тим

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

9

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

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

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


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

2
Отметил супертонский, я имел в виду общий случай. Я согласен, что в приведенном выше случае это не должно быть в отдельном классе. (+1 к вашему комментарию - это очень веский аргумент, который вы говорите о продвижении частных участников)
Джако Ван Никерк

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

3
Извините, я решил проголосовать против на основании того, что «Если вы« хотите »протестировать частные методы, это может означать, что вам нужно проголосовать против вашего дизайна». Хорошо, достаточно справедливо, но одна из причин вообще проводить тестирование заключается в том, что в крайний срок, когда у вас нет времени переосмыслить дизайн, вы пытаетесь безопасно реализовать изменение, которое необходимо внести в частный метод. В идеальном мире не нужно было бы менять этот частный метод, потому что дизайн был бы идеальным? Конечно, но в идеальном мире, но это спорный вопрос, потому что в идеальном мире, где нужны тесты, все просто работает. :)
Джон Локвуд

2
@ Джон. Очко взято, ваш отрицательный голос оправдан (+1). Спасибо за комментарий - я согласен с вами в том, что вы говорите. В таких случаях я вижу один из двух вариантов: либо метод становится закрытым для пакета, либо защищенным, а модульные тесты пишутся как обычно; или (и это насмешка и плохая практика) быстро пишется основной метод, чтобы убедиться, что он по-прежнему работает. Однако мой ответ был основан на сценарии написания НОВОГО кода, а не рефакторинга, когда вы, возможно, не сможете изменить исходный дизайн.
Jaco Van Niekerk

7
  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 вызывает method2.


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

4

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

Несколько возможных решений (AFAIK):

  1. Издеваться над вашими частными методами, но вы все равно не будете «на самом деле» тестировать свои методы.

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

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

    [Здесь вы можете протестировать частный метод, проверив изменение состояния classObj с нуля на ненулевое.]

  3. Немного отредактируйте код (надеюсь, это не устаревший код). Моя основа написания метода заключается в том, что всегда нужно что-то возвращать (int / a 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) и сделайте эти методы пакетными. Тестируемость Imo важнее конфиденциальности.


6
Единственная законная причина - протестировать часть унаследованной системы. Если вы начнете тестировать метод private / package-private, вы раскрываете внутреннюю структуру вашего объекта. Это обычно приводит к плохому исправлению кода. Используйте композицию PRefer, чтобы вы могли добиться тестируемости со всеми достоинствами объектно-ориентированной системы.
Brice

1
Согласен - это предпочтительный способ. Однако, если вы действительно хотите протестировать частные методы с помощью mockito, это единственный (типичный) вариант, который у вас есть. Однако мой ответ был немного поспешным, я должен был указать на риски, как вы и другие сделали это.
Роланд Шнайдер

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

0

В случаях, когда частный метод не является недействительным и возвращаемое значение используется в качестве параметра для метода внешней зависимости, вы можете имитировать зависимость и использовать 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.