Testarea metodei Private folosind mockito (Programare, Java, Junit, Mockito)

Nageswaran a intrebat.
public class A {

    public void method(boolean b){
          if (b == true)
               method1();
          else
               method2();
    }

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

    @Test
    public void testMethod() {
      A a = mock(A.class);
      a.method(true);
      //how to test like    verify(a).method1();
    }
}

Cum să testați metoda privată este apelată sau nu, și cum să testați metoda privată folosind mockito?????

Comentarii

  • În ciuda faptului că este specifică mockito, aceasta este în esență aceeași întrebare ca și Cum testez o funcție privată sau o clasă care are metode private, câmpuri sau clase interne private? –  > Por Raedwald.
12 răspunsuri
shift66

Nu puteți face acest lucru cu Mockito, dar puteți folosi Powermock pentru a extinde Mockito și pentru a imita metodele private. Powermock suportă Mockito. Aici‘este un exemplu.

Comentarii

    21

  • Sunt confuz cu acest răspuns. Acesta este mocking, dar titlul este testarea metodelor private –  > Por diyoda_.
  • Am folosit Powermock pentru a mock metoda privată, dar cum pot testa metoda privată cu Powermock. În cazul în care, pot să trec niște date de intrare și să aștept niște ieșiri de la metodă și apoi să verific ieșirea? –  > Por Rito.
  • Nu puteți. dacă vă bateți joc de intrare și ieșire, nu puteți testa funcționalitatea reală. –  > Por Talha.
  • @diyoda_ Autorul vrea să verifice dacă metoda este apelată, asta trebuie făcut prin mocking. Poți verifica doar mocks din păcate. –  > Por devaga.
Aravind Yarram

Nu este posibil prin mockito. De la ei wiki

De ce Mockito nu mockează metodele private?

În primul rând, nu suntem dogmatici în ceea ce privește simularea metodelor private. Pur și simplu nu ne pasă de metodele private pentru că din punctul de vedere al testării metodele private nu există. Iată câteva motive pentru care Mockito nu se preface în metode private:

Este nevoie de hacking al încărcătoarelor de clase, care nu este niciodată la îndemână și modifică API-ul (trebuie să folosiți un test runner personalizat, să adnotați clasa etc.).

Este foarte ușor de rezolvat – este suficient să schimbați vizibilitatea metodei din privată în protejată de pachet (sau protejată).

Îmi cere să petrec timp implementând & menținându-l. Și nu are sens având în vedere punctul 2 și un fapt că este deja implementat în alt instrument (powermock).

În cele din urmă… Mocking metode private este un indiciu că există ceva în neregulă cu înțelegerea OO. În OO se dorește ca obiectele (sau rolurile) să colaboreze, nu metodele. Uitați de pascal & cod procedural. Gândiți în obiecte.

Comentarii

  • Există o presupunere fatală făcută de această afirmație: >Mocking private methods is a hint that there is something wrong with OO understanding. Dacă testez o metodă publică, iar aceasta apelează metode private, aș vrea să batjocoresc returnările metodelor private. Mergând pe baza presupunerii de mai sus, se elimină necesitatea de a chiar de a implementa metode private. Cum poate fi aceasta o înțelegere deficitară a OO? –  > Por eggmatters.
  • @eggmatters Conform lui Baeldung „Tehnicile de Mocking ar trebui aplicate la dependențele externe ale clasei și nu la clasa însăși. Dacă mocking-ul metodelor private este esențial pentru testarea claselor noastre, de obicei indică o proiectare proastă.” Aici este un fir de discuție interesant despre acest subiect softwareengineering.stackexchange.com/questions/100959/… -…  > Por Jason Glez.
Mindaugas Jaraminas

Iată un mic exemplu despre cum se poate face acest lucru cu powermock

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

Pentru a testa Metoda1 utilizați codul:

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

Pentru a seta un obiect privat obj utilizați acest lucru:

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

Comentarii

  • Legătura dvs. este îndreptată doar către repo-ul de putere mock @Mindaugas –  > Por Xavier.
  • @Xavier adevărat. Îl puteți folosi dacă doriți în proiectul dvs. –  > Por Mindaugas Jaraminas.
  • Minunat !!! atât de bine explicat cu aceste exemple simple aproape totul 🙂 Pentru că scopul este doar de a testa codul și nu ceea ce oferă tot cadrul 🙂 –  > Por siddhusingh.
  • Vă rugăm să actualizați acest lucru. Whitebox nu mai face parte din API-ul public. –  > Por user447607.
AR1

Deși Mockito nu oferă această capacitate, puteți obține același rezultat folosind Mockito + clasa JUnit ReflectionUtils sau clasa Spring ReflectionTestUtils clasa. Vă rugăm să consultați un exemplu de mai jos, preluat din aici care explică modul de invocare a unei metode private:

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

Exemple complete cu ReflectionTestUtils și Mockito pot fi găsite în cartea Mockito for Spring.

Comentarii

  • ReflectionTestUtils.invokeMethod(student, „saveOrUpdate”, „argument1”, „argument2”, „argument3” ); Ultimul argument al invokeMethod, utilizează Vargs care poate primi mai multe argumente care trebuie transmise metodei private. funcționează. –  > Por Tim.
  • Acest răspuns ar trebui să aibă mai multe upvotes, de departe cel mai simplu mod de a testa metodele private. –  > Por maxeh.
Dawood ibn Kareem

Gândiți-vă la acest lucru în termeni de comportament, nu în termeni de ce metode există. Metoda numită method are un anumit comportament dacă b este adevărată. Ea are un comportament diferit dacă b este fals. Acest lucru înseamnă că trebuie să scrieți două teste diferite pentru methodunul pentru fiecare caz. Astfel, în loc să aveți trei teste orientate pe metode (unul pentru method, , unul pentru method1, , unul pentru method2, , veți avea două teste orientate pe comportament.

Legat de acest lucru (am sugerat acest lucru într-un alt fir de discuție SO recent și am fost numit un cuvânt din patru litere ca rezultat, așa că nu ezitați să luați acest lucru cu un grăunte de sare); mi se pare util să aleg nume de teste care să reflecte comportamentul pe care îl testez, mai degrabă decât numele metodei. Așadar, nu vă numiți testele testMethod(), , testMethod1(), , testMethod2() și așa mai departe. Îmi plac nume precum calculatedPriceIsBasePricePlusTax() sau taxIsExcludedWhenExcludeIsTrue() care indică ce comportament testez; apoi, în cadrul fiecărei metode de testare, testați numai comportamentul indicat. Cele mai multe astfel de comportamente vor implica doar un singur apel la o metodă publică, dar pot implica mai multe apeluri la metode private.

Sper să vă fie de ajutor.

Jaco Van Niekerk

Nu ar trebui să testați metodele private. Trebuie testate doar metodele neprivate, deoarece acestea ar trebui să apeleze oricum metodele private. Dacă „vreți” să testați metodele private, ar putea indica faptul că trebuie să vă regândiți proiectarea:

Folosesc o injecție de dependență adecvată?Este posibil să am nevoie să mut metodele private într-o clasă separată și să testez mai degrabă acea clasă?Trebuie ca aceste metode să fie private? …nu pot fi mai degrabă implicite sau protejate?

În cazul de mai sus, cele două metode care sunt numite „aleatoriu” ar putea fi de fapt necesare într-o clasă proprie, testate și apoi injectate în clasa de mai sus.

Comentarii

    28

  • Un punct de vedere valabil. Cu toate acestea, nu cumva motivul pentru care se folosește un modificator privat pentru metode nu este acela că se dorește pur și simplu să se taie codurile prea lungi și/sau repetitive? Separarea ca o altă clasă este ca și cum ai promova acele linii de coduri ca fiind cetățeni de primă clasă care nu vor mai fi reutilizate în altă parte, deoarece a fost menită special pentru a separa codurile lungi și pentru a preveni repetarea liniilor de cod. Dacă îl separați într-o altă clasă, nu este corect; ați obține cu ușurință o explozie a claselor. –  > Por supertonsky.
  • Am notat supertonsky, mă refeream la cazul general. Sunt de acord că, în cazul de mai sus, nu ar trebui să fie într-o clasă separată. (+1 la comentariul tău totuși – este un punct de vedere foarte valid pe care îl faci cu privire la promovarea membrilor privați) – –  > Por Jaco Van Niekerk.
  • @supertonsky, nu am reușit să găsesc niciun răspuns satisfăcător la această problemă. Există mai multe motive pentru care aș putea folosi membri privați și, foarte adesea, aceștia nu indică un miros de cod și aș beneficia foarte mult de testarea lor. Oamenii par să respingă acest lucru spunând „pur și simplu nu o faceți”. –  > Por LuddyPants.
  • Îmi pare rău, am optat pentru downvote bazat pe „Dacă „vrei” să testezi metodele private, poate indica faptul că trebuie să îți downvotezi designul”. OK, destul de corect, dar unul dintre motivele pentru care trebuie să testezi este pentru că, în condițiile unui termen limită în care nu ai timp să regândești designul, încerci să implementezi în siguranță o schimbare care trebuie făcută la o metodă privată. Într-o lume ideală, nu ar fi nevoie ca acea metodă privată să fie schimbată pentru că proiectul ar fi perfect? Sigur, dar într-o lume perfectă, dar este discutabil pentru că într-o lume perfectă cine are nevoie de teste, totul funcționează pur și simplu. 🙂 –  > Por John Lockwood.
  • @John. Punctul de vedere luat, downvote dvs. justificat (+1). Mulțumesc pentru comentariu, de asemenea – sunt de acord cu tine în ceea ce privește punctul de vedere pe care îl faci. În astfel de cazuri, pot vedea una dintre cele două opțiuni: Fie metoda este transformată în pachet-privat sau protejat și testele unitare sunt scrise ca de obicei; sau (și aceasta este o practică ironică și proastă) o metodă principală este scrisă rapid pentru a se asigura că încă funcționează. Cu toate acestea, răspunsul meu s-a bazat pe un scenariu de scriere a unui cod NOU și nu de refactorizare atunci când s-ar putea să nu puteți modifica proiectul inițial. –  > Por Jaco Van Niekerk.
Singh Arun
  1. Prin utilizarea reflecției, metodele private pot fi apelate din clasele de testare.În acest caz,

    //Metoda de testare va fi astfel …

    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. În cazul în care metoda privată apelează orice altă metodă privată, atunci trebuie să spionăm obiectul și să blocăm cealaltă metodă.Clasa de test va fi ca …

    //metoda de test va fi astfel …

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

**Abordarea constă în combinarea reflecției și a spionării obiectului.**Metoda1 și **metoda2 sunt metode private, iar metoda1 apelează metoda2.

Comentarii

  • Cele menționate mai sus funcționează perfect. Mulțumesc.  > Por Manish.
Abdullah Choudhury

Am reușit să testez o metodă privată în interiorul folosind mockito folosind reflecția. iată exemplul, am încercat să îl numesc astfel încât să aibă 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));

Reji

Nu prea înțeleg nevoia ta de a testa metoda privată. Problema principală este că metoda dvs. publică are void ca tip de returnare și, prin urmare, nu puteți testa metoda publică. Prin urmare, sunteți obligat să testați metoda privată. Este corectă presupunerea mea?

Câteva soluții posibile (AFAIK):

  1. Simularea metodelor dvs. private, dar tot nu veți testa „efectiv” metodele dvs.

  2. Verificarea stării obiectului utilizat în metodă. ÎN MAJORITATE, metodele fie fac o anumită procesare a valorilor de intrare și returnează o ieșire, fie schimbă starea obiectelor. De asemenea, se poate folosi testarea obiectelor pentru starea dorită.

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

    [Aici puteți testa metoda privată, verificând schimbarea stării lui classObj de la null la not null].

  3. Refaceți-vă puțin codul (sper că nu este vorba de un cod vechi). În ceea ce privește scrierea unei metode, eu cred că ar trebui să se returneze întotdeauna ceva (un int/un boolean). Valoarea returnată POATE sau NU poate fi utilizată de către implementare, dar cu siguranță va fi utilizată de către test.

    cod.

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

Fan Jin

Există de fapt o modalitate de a testa metodele dintr-un membru privat cu Mockito. Să presupunem că aveți o clasă ca aceasta:

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

Dacă doriți să testați a.method va invoca o metodă din SomeOtherClass, , puteți scrie ceva de genul de mai jos.

@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(); va înlocui membrul privat cu ceva pe care îl puteți spiona.

Roland Schneider

Puneți testul în același pachet, dar într-un folder sursă diferit (src/main/java vs. src/test/java) și faceți ca acele metode să fie private la pachet. Imo testabilitatea este mai importantă decât confidențialitatea.

Comentarii

  • Singurul motiv legitim este testarea unei părți a unui sistem moștenit. Dacă începeți să testați metode private/package-private, vă expuneți componentele interne ale obiectului. Făcând acest lucru, de obicei, rezultă un cod slab refactorizabil. Preferați compoziția, astfel încât să puteți obține testabilitatea, cu toate bunătățile unui sistem orientat pe obiecte. –  > Por Brice.
  • De acord – aceasta ar fi calea preferată. Cu toate acestea, dacă doriți cu adevărat să testați metodele private cu mockito, aceasta este singura opțiune (sigură din punct de vedere al tipurilor) pe care o aveți. Răspunsul meu a fost totuși un pic pripit, ar fi trebuit să subliniez riscurile, așa cum ați făcut-o dumneavoastră și ceilalți. –  > Por Roland Schneider.
  • Aceasta este metoda mea preferată. Nu este nimic greșit să expui obiectele interne la nivel de pachet-privat; iar testarea unitară este o testare de tip white-box, trebuie să cunoști elementele interne pentru testare. –  > Por Andrew Feng.
milensky

În cazurile în care metoda privată nu este nulă și valoarea de returnare este folosită ca parametru pentru o metodă a unei dependențe externe, puteți să vă bateți joc de dependență și să folosiți un ArgumentCaptor pentru a capta valoarea de returnare.De exemplu:

ArgumentCaptor<ByteArrayOutputStream> csvOutputCaptor = ArgumentCaptor.forClass(ByteArrayOutputStream.class);
//Do your thing..
verify(this.awsService).uploadFile(csvOutputCaptor.capture());
....
assertEquals(csvOutputCaptor.getValue().toString(), "blabla");