Test del metodo privato utilizzando mockito


104
public class A {

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

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

    @Test
    public void testMethod () {
      A a = mock (A.class);
      a.method (true);
      // come testare come verify (a) .method1 ();
    }
}

Come testare il metodo privato viene chiamato o meno e come testare il metodo privato usando mockito ???


Nonostante sia specifico per il mockito , questa è essenzialmente la stessa domanda di Come testare una funzione privata o una classe che ha metodi, campi o classi interne privati?
Raedwald

Risposte:


81

Non puoi farlo con Mockito ma puoi usare Powermock per estendere Mockito e simulare metodi privati. Powermock supporta Mockito. Ecco un esempio.


19
Sono confuso con questa risposta. Questo è beffardo, ma il titolo sta testando i metodi privati
diyoda_

Ho usato Powermock per deridere il metodo privato, ma come posso testare il metodo privato con Powermock. Dove posso passare un input e aspettarmi un output dal metodo e quindi verificare l'output?
Rito

Non puoi. Simuli l'output di input, non puoi testare la reale funzionalità.
Talha

131

Non possibile tramite mockito. Dal loro wiki

Perché Mockito non prende in giro i metodi privati?

In primo luogo, non siamo dogmatici nel deridere i metodi privati. Semplicemente non ci interessano i metodi privati ​​perché dal punto di vista della verifica dei metodi privati ​​non esistono. Ecco un paio di motivi per cui Mockito non prende in giro i metodi privati:

Richiede l'hacking di classloader che non è mai a prova di proiettile e cambia l'API (è necessario utilizzare un test runner personalizzato, annotare la classe, ecc.).

È molto facile da risolvere: basta cambiare la visibilità del metodo da privato a protetto da pacchetto (o protetto).

Mi richiede di dedicare tempo all'implementazione e alla manutenzione. E non ha senso dato il punto n. 2 e il fatto che sia già implementato in uno strumento diverso (powermock).

Infine ... Schernire i metodi privati ​​è un indizio che c'è qualcosa di sbagliato nella comprensione dell'OO. In OO vuoi che gli oggetti (o ruoli) collaborino, non i metodi. Dimentica pascal e codice procedurale. Pensa agli oggetti.


1
C'è un presupposto fatale fatto da questa affermazione:> Schernire metodi privati ​​è un indizio che c'è qualcosa di sbagliato nella comprensione dell'OO. Se sto testando un metodo pubblico e chiama metodi privati, vorrei simulare i risultati del metodo privato. Il presupposto di cui sopra evita la necessità di implementare anche metodi privati. Com'è una scarsa comprensione di OO?
pasta d'uovo l'

1
@eggmatters Secondo Baeldung "Le tecniche di derisione dovrebbero essere applicate alle dipendenze esterne della classe e non alla classe stessa. Se deridere i metodi privati ​​è essenziale per testare le nostre classi, di solito indica un cattivo design." Ecco un interessante thread su di esso softwareengineering.stackexchange.com/questions/100959/…
Jason Glez

34

Ecco un piccolo esempio di come farlo con powermock

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

Per testare il metodo1 usa il codice:

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

Per impostare oggetto privato obj utilizzare questo:

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

Il tuo link punta solo al power mock repo @Mindaugas
Xavier

@Xavier vero. Puoi usarlo se ti piace nel tuo progetto.
Mindaugas Jaraminas

1
Meraviglioso !!! così ben spiegato con questi semplici esempi quasi tutto :) Perché lo scopo è solo quello di testare il codice e non quello che fornisce tutto il framework :)
siddhusingh

Per favore aggiorna questo. Whitebox non fa più parte dell'API pubblica.
user447607

17

Pensa a questo in termini di comportamento, non in termini di metodi disponibili. Il metodo chiamato methodha un comportamento particolare se bè vero. Ha un comportamento diverso se bè falso. Ciò significa che dovresti scrivere due diversi test per method; uno per ogni caso. Quindi, invece di avere tre test orientati al metodo (uno per method, uno per method1, uno per method2, hai due test orientati al comportamento.

In relazione a questo (recentemente l'ho suggerito in un altro thread SO, e come risultato sono stato chiamato una parola di quattro lettere, quindi sentiti libero di prenderlo con le pinze); Trovo utile scegliere nomi di test che riflettano il comportamento che sto testando, piuttosto che il nome del metodo. Quindi non chiamare i tuoi test testMethod(), testMethod1(), testMethod2()e così via. Mi piacciono i nomi come calculatedPriceIsBasePricePlusTax()o taxIsExcludedWhenExcludeIsTrue()che indicano quale comportamento sto testando; quindi all'interno di ciascun metodo di prova, testare solo il comportamento indicato. La maggior parte di tali comportamenti coinvolgerà solo una chiamata a un metodo pubblico, ma potrebbe coinvolgere molte chiamate a metodi privati.

Spero che questo ti aiuti.


13

Sebbene Mockito non fornisca tale funzionalità, è possibile ottenere lo stesso risultato utilizzando Mockito + la classe JUnit ReflectionUtils o la classe Spring ReflectionTestUtils . Si prega di vedere un esempio di seguito tratto da qui che spiega come invocare un metodo privato:

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

Esempi completi con ReflectionTestUtils e Mockito possono essere trovati nel libro Mockito for Spring


ReflectionTestUtils.invokeMethod (student, "saveOrUpdate", "argomento1", "argomento2", "argomento3"); L'ultimo argomento di invokeMethod utilizza Vargs che può accettare più argomenti che devono essere passati al metodo privato. Funziona.
Tim

Questa risposta dovrebbe avere più voti positivi, di gran lunga il modo più semplice per testare i metodi privati.
Max

9

Non dovresti testare metodi privati. Solo i metodi non privati ​​devono essere testati poiché questi dovrebbero comunque chiamare i metodi privati. Se "vuoi" testare metodi privati, potrebbe indicare che devi ripensare il tuo design:

Sto usando una corretta iniezione delle dipendenze? Devo forse spostare i metodi privati ​​in una classe separata e piuttosto testarlo? Questi metodi devono essere privati? ... non possono essere predefiniti o protetti piuttosto?

Nell'istanza precedente, i due metodi che sono chiamati "casualmente" potrebbero effettivamente dover essere collocati in una loro classe, testati e quindi iniettati nella classe precedente.


26
Un punto valido. Tuttavia, non è il motivo per cui si utilizza un modificatore privato per i metodi perché si desidera semplicemente abbattere codici troppo lunghi e / o ripetitivi? Separarlo come un'altra classe è come se stessi promuovendo quelle linee di codici per essere cittadini di prima classe che non verranno riutilizzati da nessun'altra parte perché era pensata specificamente per partizionare codici lunghi e per impedire la ripetizione di righe di codici. Se hai intenzione di separarlo in un'altra classe, non ti sembra giusto; otterresti facilmente un'esplosione di classe.
supertonsky

2
Notato supertonsky, mi riferivo al caso generale. Sono d'accordo che nel caso precedente non dovrebbe essere in una classe separata. (+1 sul tuo commento però - è un punto molto valido che stai facendo sulla promozione dei membri privati)
Jaco Van Niekerk

4
@supertonsky, non sono riuscito a trovare una risposta soddisfacente a questo problema. Ci sono diversi motivi per cui potrei usare membri privati ​​e molto spesso non indicano un odore di codice e trarrei grande beneficio dal testarli. La gente sembra spazzare via questo dicendo "semplicemente non farlo".
LuddyPants

3
Spiacenti, ho scelto di votare in negativo in base a "Se" vuoi "testare metodi privati, potrebbe indicare che devi votare il tuo design". OK, abbastanza equo, ma uno dei motivi per eseguire test è perché, in una scadenza in cui non hai tempo per ripensare il design, stai cercando di implementare in sicurezza una modifica che deve essere apportata a un metodo privato. In un mondo ideale non sarebbe necessario cambiare quel metodo privato perché il design sarebbe perfetto? Certo, ma in un mondo perfetto, ma è discutibile perché in un mondo perfetto che ha bisogno di test, funziona tutto. :)
John Lockwood

2
@John. Punto preso, il tuo voto negativo è garantito (+1). Grazie anche per il commento - sono d'accordo con te sul punto che fai. In questi casi posso vedere una delle due opzioni: o il metodo è reso privato dal pacchetto o protetto e gli unit test scritti come al solito; oppure (e questo è ironico e una cattiva pratica) viene scritto rapidamente un metodo principale per assicurarsi che funzioni ancora. Tuttavia, la mia risposta si è basata su uno scenario di NUOVO codice scritto e non refactoring quando potresti non essere in grado di manomettere il design originale.
Jaco Van Niekerk

6

Sono stato in grado di testare un metodo privato all'interno usando mockito usando la riflessione. Ecco l'esempio, ho provato a nominarlo in modo che abbia un senso

//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. Utilizzando la reflection, i metodi privati ​​possono essere chiamati dalle classi di test. In questo caso,

    // il metodo di test sarà così ...

    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. Se il metodo privato chiama un altro metodo privato, allora dobbiamo spiare l'oggetto e stub l'altro metodo.La classe di test sarà come ...

    // il metodo di test sarà così ...

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

** L'approccio consiste nel combinare la riflessione e lo spionaggio dell'oggetto. ** metodo1 e ** metodo2 sono metodi privati ​​e metodo1 chiama metodo2.


4

Non capisco davvero la tua necessità di testare il metodo privato. Il problema principale è che il tuo metodo pubblico ha void come tipo di ritorno, e quindi non sei in grado di testare il tuo metodo pubblico. Quindi sei costretto a testare il tuo metodo privato. La mia ipotesi è corretta ??

Alcune possibili soluzioni (AFAIK):

  1. Schernendo i tuoi metodi privati, ma ancora non testerai "effettivamente" i tuoi metodi.

  2. Verificare lo stato dell'oggetto utilizzato nel metodo. PRINCIPALMENTE i metodi eseguono una certa elaborazione dei valori di input e restituiscono un output o modificano lo stato degli oggetti. Può anche essere impiegato il test degli oggetti per lo stato desiderato.

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

    [Qui puoi testare il metodo privato, controllando il cambio di stato di classObj da null a non null.]

  3. Rifattorizza un po 'il tuo codice (spero che questo non sia un codice legacy). Il mio fondamento per scrivere un metodo è che si dovrebbe sempre restituire qualcosa (un int / a booleano). Il valore restituito MAGGIO o NON PU essere utilizzato dall'implementazione, ma SARÀ SICURAMENTE utilizzato dal test

    codice.

    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

In realtà c'è un modo per testare i metodi di un membro privato con Mockito. Supponiamo che tu abbia una classe come questa:

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

Se vuoi testare a.methodrichiamerà un metodo da SomeOtherClass, puoi scrivere qualcosa come di seguito.

@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(); stub il membro privato con qualcosa che puoi spiare.


2

Metti il ​​tuo test nello stesso pacchetto, ma in una cartella di origine diversa (src / main / java vs. src / test / java) e rendi questi metodi privati ​​del pacchetto. La testabilità di Imo è più importante della privacy.


6
L'unico motivo legittimo è testare una parte di un sistema legacy. Se inizi a testare il metodo privato / pacchetto privato, esponi i tuoi oggetti interni. In questo modo di solito si ottiene un codice refactoring scadente. Preferisci la composizione in modo da ottenere la testabilità, con tutta la bontà di un sistema orientato agli oggetti.
Brice

1
D'accordo: questo sarebbe il modo preferito. Tuttavia, se vuoi davvero testare metodi privati ​​con mockito, questa è l'unica opzione (typesafe) che hai. La mia risposta però è stata un po 'frettolosa, avrei dovuto segnalare i rischi, come avete fatto tu e gli altri.
Roland Schneider

Questo è il mio modo preferito. Non c'è niente di sbagliato nell'esporre oggetti interni a livello di pacchetto privato; e unit test è il test white-box, è necessario conoscere l'interno per il test.
Andrew Feng

0

Nei casi in cui il metodo privato non è void e il valore restituito viene utilizzato come parametro per il metodo di una dipendenza esterna, è possibile simulare la dipendenza e utilizzare un ArgumentCaptorper acquisire il valore restituito. Per esempio:

ArgumentCaptor<ByteArrayOutputStream> csvOutputCaptor = ArgumentCaptor.forClass(ByteArrayOutputStream.class);
//Do your thing..
verify(this.awsService).uploadFile(csvOutputCaptor.capture());
....
assertEquals(csvOutputCaptor.getValue().toString(), "blabla");
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.