Testowanie metody prywatnej przy użyciu mockito

104
klasa publiczna A {

    public void method (boolean b) {
          jeśli (b == prawda)
               metoda1 ();
          jeszcze
               metoda2 ();
    }

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

    @Test
    public void testMethod () {
      A a = mock (klasa A.);
      a. metoda (prawda);
      // jak testować jak verify (a) .method1 ();
    }
}

Jak testować metodę prywatną jest wywoływana czy nie, a jak testować metodę prywatną używając mockito ???

Nageswaran
źródło
Pomimo tego , że jest to specyficzne dla mockito , jest to w zasadzie to samo pytanie, co Jak przetestować funkcję prywatną lub klasę, która ma prywatne metody, pola lub klasy wewnętrzne?
Raedwald

Odpowiedzi:

81

Nie możesz tego zrobić z Mockito, ale możesz użyć Powermock, aby rozszerzyć Mockito i udawać metody prywatne. Powermock obsługuje Mockito. Oto przykład.

shift66
źródło
19
Jestem zdezorientowany tą odpowiedzią. To kpina, ale tytuł testuje prywatne metody
diyoda_
Użyłem Powermock do kpiny z metody prywatnej, ale jak mogę przetestować metodę prywatną za pomocą Powermock. Gdzie mogę przekazać pewne dane wejściowe i oczekiwać wyników z metody, a następnie zweryfikować dane wyjściowe?
Rito
Nie możesz, udajesz wyjście z wejścia, nie możesz przetestować prawdziwej funkcjonalności.
Talha
131

Niemożliwe przez mockito. Z ich wiki

Dlaczego Mockito nie kpi z prywatnych metod?

Po pierwsze, nie jesteśmy dogmatyczni, jeśli chodzi o kpienie z prywatnych metod. Po prostu nie dbamy o metody prywatne, ponieważ z punktu widzenia testowania metody prywatne nie istnieją. Oto kilka powodów, dla których Mockito nie kpi z prywatnych metod:

Wymaga hakowania programów ładujących klasy, które nigdy nie są kuloodporne i zmienia interfejs API (musisz użyć niestandardowego modułu uruchamiającego testy, dodać adnotację do klasy itp.).

Jest to bardzo łatwe do obejścia - wystarczy zmienić widoczność metody z prywatnej na chronioną pakietem (lub chronioną).

Wymaga to poświęcenia czasu na jego wdrożenie i utrzymanie. I nie ma to sensu biorąc pod uwagę punkt # 2 i fakt, że jest już zaimplementowany w innym narzędziu (powermock).

Wreszcie ... Kpiny z prywatnych metod to wskazówka, że ​​coś jest nie tak ze zrozumieniem OO. W OO chcesz, aby współpracowały ze sobą obiekty (lub role), a nie metody. Zapomnij o pascalu i kodzie proceduralnym. Myśl w przedmiotach.

Aravind Yarram
źródło
2
Stwierdzenie to zawiera fatalne założenie:> Kpienie z prywatnych metod jest wskazówką, że coś jest nie tak ze zrozumieniem OO. Jeśli testuję metodę publiczną i wywołuje ona metody prywatne, chciałbym naśladować zwroty metod prywatnych. Podążanie za powyższym założeniem eliminuje potrzebę nawet wdrażania metod prywatnych. Jak to słabe zrozumienie OO?
Eggmatters
1
@eggmatters Według Baeldunga „Techniki mockowania powinny być stosowane do zewnętrznych zależności klasy, a nie do samej klasy. Jeśli kpienie z metod prywatnych jest niezbędne do testowania naszych klas, zwykle oznacza to zły projekt”. Oto fajny wątek na ten temat softwareengineering.stackexchange.com/questions/100959/ ...
Jason Glez
35

Oto mały przykład, jak to zrobić z powermockiem

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

Aby przetestować metodę 1 użyj kodu:

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

Aby ustawić prywatny obiekt OBJ użyj tego:

Hello testObj = new Hello();
Hello newObject = new Hello();
Whitebox.setInternalState(testObj, "obj", newObject);
Mindaugas Jaraminas
źródło
Twój link wskazuje tylko na makietę repozytorium mocy @Mindaugas
Xavier
@Xavier true. Możesz go użyć, jeśli chcesz w swoim projekcie.
Mindaugas Jaraminas
1
Wspaniale !!! tak dobrze wyjaśnione tym prostym przykładem prawie wszystko :) Ponieważ celem jest tylko przetestowanie kodu, a nie tego, co zapewnia cały framework :)
siddhusingh
Zaktualizuj to. Whitebox nie jest już częścią publicznego API.
user447607
17

Pomyśl o tym w kategoriach zachowania, a nie w kategoriach dostępnych metod. Wywołana metoda methodma określone zachowanie, jeśli bjest prawdziwe. Zachowuje się inaczej, jeśli bjest fałszywe. Oznacza to, że powinieneś napisać dwa różne testy method; po jednym w każdym przypadku. Więc zamiast trzech testów zorientowanych na metodę (jeden za method, jeden za method1, jeden za method2, masz dwa testy zorientowane na zachowanie).

Związane z tym (zasugerowałem to niedawno w innym wątku SO, iw rezultacie zostałem nazwany czteroliterowym słowem, więc możesz to wziąć z przymrużeniem oka); Uważam, że pomocne jest wybranie nazw testów, które odzwierciedlają zachowanie, które testuję, zamiast nazwy metody. Więc nie nazywaj swoje testy testMethod(), testMethod1(), testMethod2()i tak dalej. Lubię takie imionacalculatedPriceIsBasePricePlusTax() lub, taxIsExcludedWhenExcludeIsTrue()które wskazują, jakie zachowanie testuję; następnie w ramach każdej metody testowej testuj tylko wskazane zachowanie. Większość takich zachowań obejmuje tylko jedno wywołanie metody publicznej, ale może obejmować wiele wywołań metod prywatnych.

Mam nadzieję że to pomoże.

Dawood ibn Kareem
źródło
15

Chociaż Mockito nie zapewnia takiej możliwości, ten sam wynik można osiągnąć za pomocą Mockito + klasy JUnit ReflectionUtils lub klasy Spring ReflectionTestUtils . Zobacz poniższy przykład zaczerpnięty stąd, wyjaśniający, jak wywołać metodę prywatną:

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

Pełne przykłady z ReflectionTestUtils i Mockito można znaleźć w książce Mockito na wiosnę

AR1
źródło
ReflectionTestUtils.invokeMethod (student, "saveOrUpdate", "argument1", "argument2", "argument3"); Ostatni argument metody invokeMethod wykorzystuje Vargs, który może przyjmować wiele argumentów, które należy przekazać do metody prywatnej. to działa.
Tim
Ta odpowiedź powinna mieć więcej głosów za, zdecydowanie najłatwiejszy sposób na przetestowanie prywatnych metod.
Max
9

Nie powinieneś testować prywatnych metod. Należy przetestować tylko metody nieprywatne, ponieważ i tak powinny one wywoływać metody prywatne. Jeśli „chcesz” przetestować metody prywatne, może to oznaczać, że musisz przemyśleć swój projekt:

Czy używam odpowiedniego wstrzyknięcia zależności? Czy muszę przenieść metody prywatne do osobnej klasy i raczej to przetestować? Czy te metody muszą być prywatne? ... czy nie mogą być domyślne czy raczej chronione?

W powyższym przypadku dwie metody, które są nazywane „losowo”, mogą w rzeczywistości wymagać umieszczenia w osobnej klasie, przetestowania, a następnie wstrzyknięcia do klasy powyżej.

Jaco Van Niekerk
źródło
27
Ważny punkt. Jednak czy to nie powodem używania prywatnego modyfikatora dla metod jest to, że chcesz po prostu ograniczyć kody, które są zbyt długie i / lub powtarzalne? Oddzielenie go jako innej klasy jest tak, jakbyś promował te linie kodów jako obywateli pierwszej klasy, których nie można ponownie wykorzystać nigdzie indziej, ponieważ zostało to przeznaczone specjalnie do dzielenia długich kodów i zapobiegania powtarzaniu się wierszy kodów. Jeśli masz zamiar podzielić to na inną klasę, po prostu nie wydaje się to właściwe; łatwo byś miał wybuch klasy.
supertonsky
2
Zauważony jako supertonsky, odnosiłem się do przypadku ogólnego. Zgadzam się, że w powyższym przypadku nie powinno to być w osobnej klasie. (+1 w komentarzu - to bardzo ważny punkt, który robisz przy promowaniu prywatnych członków)
Jaco Van Niekerk,
4
@supertonsky, nie mogłem znaleźć zadowalającej odpowiedzi na ten problem. Jest kilka powodów, dla których mógłbym korzystać z prywatnych członków i bardzo często nie sygnalizują oni zapachu kodu, a testowanie ich przyniosłoby wiele korzyści. Ludzie zdają się zbywać to, mówiąc „po prostu tego nie rób”.
LuddyPants
3
Przepraszam, zdecydowałem się na głosowanie przeciwne na podstawie „Jeśli 'chcesz' przetestować metody prywatne, może to oznaczać, że musisz odrzucić swój projekt”. OK, w porządku, ale jednym z powodów, dla których w ogóle warto testować, jest to, że w ostatecznym terminie, kiedy nie masz czasu na ponowne przemyślenie projektu, próbujesz bezpiecznie wprowadzić zmianę, którą należy wprowadzić w metoda prywatna. Czy w idealnym świecie ta prywatna metoda nie musiałaby być zmieniana, ponieważ projekt byłby doskonały? Jasne, ale w idealnym świecie, ale jest to dyskusyjne, ponieważ w idealnym świecie, który potrzebuje testów, wszystko po prostu działa. :)
John Lockwood
2
@Jan. Punkt zajęty, Twój głos przeciwny jest uzasadniony (+1). Dziękuję również za komentarz - zgadzam się z tobą w przedstawionym przez ciebie punkcie. W takich przypadkach widzę jedną z dwóch opcji: albo metoda jest ustawiana jako prywatna dla pakietu lub chroniona, a testy jednostkowe są pisane jak zwykle; lub (i jest to żartobliwy i zła praktyka) szybko spisuje się główną metodę, aby upewnić się, że nadal działa. Jednak moja odpowiedź opierała się na scenariuszu pisania NOWEGO kodu, a nie jego refaktoryzacji, kiedy możesz nie być w stanie manipulować oryginalnym projektem.
Jaco Van Niekerk
7
  1. Korzystając z odbicia, można wywołać metody prywatne z klas testowych. W tym przypadku,

    // metoda testowa będzie taka ...

    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. Jeśli metoda prywatna wywoła jakąkolwiek inną metodę prywatną, musimy szpiegować obiekt i zablokować inną metodę. Klasa testowa będzie wyglądać następująco ...

    // metoda testowa będzie taka ...

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

** Podejście polega na połączeniu refleksji i podglądania obiektu. ** metoda1 i ** metoda2 to metody prywatne, a metoda1 wywołuje metodę2.

Singh Arun
źródło
6

Udało mi się przetestować prywatną metodę wewnątrz za pomocą mockito z wykorzystaniem refleksji. Oto przykład, próbowałem nazwać go tak, aby miał sens

//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));
Abdullah Choudhury
źródło
4

Naprawdę nie rozumiem twojej potrzeby testowania metody prywatnej. Główny problem polega na tym, że twoja metoda publiczna ma void jako typ zwracany i dlatego nie możesz przetestować swojej metody publicznej. Dlatego jesteś zmuszony przetestować swoją prywatną metodę. Czy moje przypuszczenie jest prawidłowe?

Kilka możliwych rozwiązań (AFAIK):

  1. Kpiąc ze swoich prywatnych metod, ale nadal nie będziesz „faktycznie” testować swoich metod.

  2. Sprawdź stan obiektu używanego w metodzie. MOSTLY metody albo wykonują pewne przetwarzanie wartości wejściowych i zwracają dane wyjściowe, albo zmieniają stan obiektów. Można również zastosować testowanie obiektów pod kątem pożądanego stanu.

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

    [Tutaj możesz przetestować metodę prywatną, sprawdzając zmianę stanu classObj z null na not null.]

  3. Zmień trochę kod (mam nadzieję, że nie jest to starszy kod). Moją podstawą pisania metody jest to, że zawsze należy coś zwracać (int / a boolean). Zwrócona wartość MOŻE lub NIE MOŻE zostać użyta przez implementację, ale PEWNIE BĘDZIE wykorzystana przez test

    kod.

    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() {}
    
    }
Reji
źródło
3

W rzeczywistości istnieje sposób na przetestowanie metod od prywatnego członka za pomocą Mockito. Powiedzmy, że masz taką klasę:

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() {}
}

Jeśli chcesz, aby test a.methodwywołał metodę z SomeOtherClass, możesz napisać coś takiego jak poniżej.

@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(); zablokuje prywatnego członka czymś, co możesz szpiegować.

Fan Jin
źródło
2

Umieść test w tym samym pakiecie, ale w innym folderze źródłowym (src / main / java vs. src / test / java) i ustaw te metody jako pakiet-prywatny. Testowalność Imo jest ważniejsza niż prywatność.

Roland Schneider
źródło
6
Jedynym uzasadnionym powodem jest przetestowanie części starszego systemu. Jeśli zaczniesz testować metodę private / package-private, ujawnisz wewnętrzne elementy obiektu. Takie postępowanie zazwyczaj skutkuje słabym kodem podlegającym refaktoryzacji. Wcześniej przygotuj kompozycję, aby uzyskać testowalność z całą zaletą systemu zorientowanego obiektowo.
Brice,
1
Zgoda - to preferowany sposób. Jeśli jednak naprawdę chcesz przetestować metody prywatne za pomocą mockito, jest to jedyna (bezpieczna) opcja, którą masz. Moja odpowiedź była jednak nieco pochopna, powinienem był wskazać na ryzyko, tak jak ty i inni to zrobiliście.
Roland Schneider,
To jest mój ulubiony sposób. Nie ma nic złego w ujawnianiu wewnętrznego obiektu na poziomie prywatnym pakietu; a test jednostkowy to test białoskrzynkowy, do testowania musisz znać wewnętrzne.
Andrew Feng
0

W przypadkach, gdy metoda prywatna nie jest void, a wartość zwracana jest używana jako parametr metody zależności zewnętrznej, można mockować zależność i użyć znaku ArgumentCaptordo przechwycenia wartości zwracanej. Na przykład:

ArgumentCaptor<ByteArrayOutputStream> csvOutputCaptor = ArgumentCaptor.forClass(ByteArrayOutputStream.class);
//Do your thing..
verify(this.awsService).uploadFile(csvOutputCaptor.capture());
....
assertEquals(csvOutputCaptor.getValue().toString(), "blabla");
mleczny
źródło