Testen der privaten Methode mit mockito

104
öffentliche Klasse A {

    public void method (boolean b) {
          if (b == true)
               Methode 1();
          sonst
               Methode2 ();
    }}

    private void method1 () {}
    private void method2 () {}
}}
öffentliche Klasse TestA {

    @Prüfung
    public void testMethod () {
      A a = Schein (A. Klasse);
      a.method (true);
      // wie man testet wie verify (a) .method1 ();
    }}
}}

Wie man eine private Methode testet oder nicht, und wie man eine private Methode mit mockito testet ???

Nageswaran
quelle
Obwohl dies mockito- spezifisch ist, ist dies im Wesentlichen dieselbe Frage wie Wie
Raedwald

Antworten:

81

Mit Mockito können Sie das nicht tun, aber Sie können Powermock verwenden , um Mockito zu erweitern und private Methoden zu verspotten. Powermock unterstützt Mockito. Hier ist ein Beispiel.

shift66
quelle
19
Ich bin mit dieser Antwort verwirrt. Das ist spöttisch, aber der Titel testet die privaten Methoden
diyoda_
Ich habe Powermock verwendet, um die private Methode zu verspotten, aber wie kann ich die private Methode mit Powermock testen? Wo kann ich eine Eingabe übergeben und eine Ausgabe von der Methode erwarten und dann die Ausgabe überprüfen?
Rito
Sie können nicht. Sie verspotten Eingabe Ausgabe, Sie können die reale Funktionalität nicht testen.
Talha
131

Nicht möglich durch Mockito. Aus ihrem Wiki

Warum verspottet Mockito keine privaten Methoden?

Erstens sind wir nicht dogmatisch, wenn es darum geht, private Methoden zu verspotten. Private Methoden interessieren uns einfach nicht, da es vom Standpunkt des Testens keine privaten Methoden gibt. Hier sind einige Gründe, warum Mockito private Methoden nicht verspottet:

Es erfordert das Hacken von Klassenladeprogrammen, das niemals kugelsicher ist, und es ändert die API (Sie müssen einen benutzerdefinierten Testläufer verwenden, die Klasse mit Anmerkungen versehen usw.).

Es ist sehr einfach zu umgehen - ändern Sie einfach die Sichtbarkeit der Methode von privat zu paketgeschützt (oder geschützt).

Ich muss Zeit damit verbringen, es zu implementieren und zu warten. Und es macht angesichts von Punkt 2 und der Tatsache, dass es bereits in einem anderen Tool (Powermock) implementiert ist, keinen Sinn.

Schließlich ... Das Verspotten privater Methoden ist ein Hinweis darauf, dass mit dem OO-Verständnis etwas nicht stimmt. In OO möchten Sie, dass Objekte (oder Rollen) zusammenarbeiten, nicht Methoden. Vergessen Sie Pascal- und Prozedurcode. Denken Sie in Objekten.

Aravind Yarram
quelle
1
Diese Aussage enthält eine fatale Annahme:> Das Verspotten privater Methoden ist ein Hinweis darauf, dass mit dem OO-Verständnis etwas nicht stimmt. Wenn ich eine öffentliche Methode teste und private Methoden aufruft, möchte ich private Methodenrückgaben verspotten. Wenn Sie sich an die obige Annahme halten, müssen Sie nicht einmal private Methoden implementieren . Wie ist das ein schlechtes Verständnis von OO?
Eggmatters
1
@eggmatters Laut Baeldung "sollten Spotttechniken auf die externen Abhängigkeiten der Klasse und nicht auf die Klasse selbst angewendet werden. Wenn das Verspotten privater Methoden für das Testen unserer Klassen unerlässlich ist, weist dies normalerweise auf ein schlechtes Design hin." Hier ist ein cooler Thread dazu softwareengineering.stackexchange.com/questions/100959/…
Jason Glez
34

Hier ist ein kleines Beispiel, wie man es mit Powermock macht

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

Um Methode1 zu testen, verwenden Sie Code:

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

Verwenden Sie Folgendes, um ein privates Objektobjekt festzulegen :

Hello testObj = new Hello();
Hello newObject = new Hello();
Whitebox.setInternalState(testObj, "obj", newObject);
Mindaugas Jaraminas
quelle
Ihr Link zeigt nur auf das Power Mock Repo @Mindaugas
Xavier
@ Xavier wahr. Sie können es verwenden, wenn Sie in Ihrem Projekt möchten.
Mindaugas Jaraminas
1
Wunderbar !!! so gut erklärt mit diesem einfachen Beispiel fast alles :) Weil der Zweck nur darin besteht, den Code zu testen und nicht das, was das gesamte Framework bietet :)
siddhusingh
Bitte aktualisieren Sie dies. Whitebox ist nicht mehr Teil der öffentlichen API.
user447607
17

Denken Sie darüber in Bezug auf das Verhalten nach, nicht in Bezug auf die Methoden, die es gibt. Die aufgerufene Methode methodhat ein bestimmtes Verhalten, wenn sie bwahr ist. Es hat ein anderes Verhalten, wenn bes falsch ist. Dies bedeutet, dass Sie zwei verschiedene Tests für schreiben sollten method; eine für jeden Fall. Anstatt drei methodenorientierte Tests zu haben (einen für method, einen für method1, einen für method2, einen für , haben Sie zwei verhaltensorientierte Tests.

Im Zusammenhang damit (ich habe dies kürzlich in einem anderen SO-Thread vorgeschlagen und als Ergebnis ein Wort aus vier Buchstaben genannt, also zögern Sie nicht, dies mit einem Körnchen Salz zu nehmen); Ich finde es hilfreich, Testnamen zu wählen, die das Verhalten widerspiegeln, das ich teste, und nicht den Namen der Methode. So rufen Sie nicht Ihre Tests testMethod(), testMethod1(), testMethod2()und so weiter. Ich mag Namen wie calculatedPriceIsBasePricePlusTax()odertaxIsExcludedWhenExcludeIsTrue() die anzeigen, welches Verhalten ich teste; Testen Sie dann innerhalb jeder Testmethode nur das angegebene Verhalten. Die meisten dieser Verhaltensweisen umfassen nur einen Aufruf einer öffentlichen Methode, können jedoch viele Aufrufe privater Methoden beinhalten.

Hoffe das hilft.

Dawood ibn Kareem
quelle
12

Obwohl Mockito diese Funktion nicht bietet, können Sie mit Mockito + der JUnit ReflectionUtils-Klasse oder der Spring ReflectionTestUtils- Klasse dasselbe Ergebnis erzielen . Bitte beachten Sie ein Beispiel unten genommen von hier zu erklären , wie eine private Methode aufzurufen:

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

Vollständige Beispiele mit ReflectionTestUtils und Mockito finden Sie im Buch Mockito for Spring

AR1
quelle
ReflectionTestUtils.invokeMethod (Student, "saveOrUpdate", "argument1", "argument2", "argument3"); Das letzte Argument von invokeMethod verwendet Vargs, das mehrere Argumente annehmen kann, die an die private Methode übergeben werden müssen. Es klappt.
Tim
Diese Antwort sollte mehr positive Stimmen haben, bei weitem der einfachste Weg, private Methoden zu testen.
Max
9

Sie sollten keine privaten Methoden testen. Es müssen nur nicht private Methoden getestet werden, da diese die privaten Methoden sowieso aufrufen sollten. Wenn Sie private Methoden "testen" möchten, bedeutet dies möglicherweise, dass Sie Ihr Design überdenken müssen:

Benutze ich die richtige Abhängigkeitsinjektion? Muss ich möglicherweise die privaten Methoden in eine separate Klasse verschieben und das lieber testen? Müssen diese Methoden privat sein? ... können sie nicht standardmäßig oder eher geschützt sein?

In dem obigen Fall müssen die beiden Methoden, die als "zufällig" bezeichnet werden, möglicherweise tatsächlich in eine eigene Klasse eingeordnet, getestet und dann in die obige Klasse injiziert werden.

Jaco Van Niekerk
quelle
26
Ein gültiger Punkt. Ist es nicht der Grund für die Verwendung eines privaten Modifikators für Methoden, dass Sie einfach zu lange und / oder sich wiederholende Codes zerlegen möchten? Wenn Sie es als eine andere Klasse trennen, fördern Sie diese Codezeilen als Bürger erster Klasse, die nirgendwo anders wiederverwendet werden, da sie speziell dazu gedacht waren, lange Codes zu partitionieren und zu verhindern, dass sich Codezeilen wiederholen. Wenn Sie es in eine andere Klasse aufteilen, fühlt es sich einfach nicht richtig an. Sie würden leicht Klassenexplosion bekommen.
Supertonsky
2
Als Supertonsky bezeichnet, bezog ich mich auf den allgemeinen Fall. Ich bin damit einverstanden, dass es im obigen Fall nicht in einer separaten Klasse sein sollte. (+1 auf Ihren Kommentar - es ist ein sehr gültiger Punkt, den Sie zur Förderung privater Mitglieder machen)
Jaco Van Niekerk
4
@supertonsky, ich konnte keine zufriedenstellende Antwort auf dieses Problem finden. Es gibt mehrere Gründe, warum ich private Mitglieder verwenden könnte, und sehr oft weisen sie nicht auf einen Codegeruch hin, und ich würde stark davon profitieren, sie zu testen. Die Leute scheinen dies abzuschwächen, indem sie sagen "Tu es einfach nicht".
LuddyPants
3
Entschuldigung, ich habe mich für eine Abwertung entschieden, basierend auf "Wenn Sie private Methoden testen möchten, kann dies bedeuten, dass Sie Ihr Design ablehnen müssen". OK, fair genug, aber einer der Gründe, überhaupt zu testen, ist, dass Sie innerhalb einer Frist, in der Sie keine Zeit haben, das Design zu überdenken, versuchen, eine Änderung, die an a vorgenommen werden muss, sicher umzusetzen private Methode. In einer idealen Welt müsste diese private Methode nicht geändert werden, weil das Design perfekt wäre? Sicher, aber in einer perfekten Welt, aber es ist umstritten, denn in einer perfekten Welt, die Tests benötigt, funktioniert alles einfach. :)
John Lockwood
2
@John. Punkt genommen, Ihre Abwertung ist gerechtfertigt (+1). Vielen Dank auch für den Kommentar - ich stimme Ihnen in dem Punkt zu, den Sie ansprechen. In solchen Fällen kann ich eine von zwei Optionen sehen: Entweder wird die Methode paketprivat oder geschützt gemacht und Unit-Tests werden wie gewohnt geschrieben; oder (und das ist ein Augenzwinkern und eine schlechte Praxis) eine Hauptmethode wird schnell geschrieben, um sicherzustellen, dass sie immer noch funktioniert. Meine Antwort basierte jedoch auf einem Szenario, in dem NEUER Code geschrieben und nicht umgestaltet wurde, wenn Sie möglicherweise nicht in der Lage sind, das ursprüngliche Design zu manipulieren.
Jaco Van Niekerk
6

Ich konnte eine private Methode mit Mockito unter Verwendung von Reflexion testen. Hier ist das Beispiel, das versucht wurde, es so zu benennen, dass es Sinn macht

//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
quelle
6
  1. Durch die Verwendung von Reflection können private Methoden aus Testklassen aufgerufen werden. In diesem Fall,

    // Testmethode wird so sein ...

    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. Wenn die private Methode eine andere private Methode aufruft, müssen wir das Objekt ausspionieren und die andere Methode stubben. Die Testklasse sieht aus wie ...

    // Testmethode wird so sein ...

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

** Der Ansatz besteht darin, Reflexion und Spionage des Objekts zu kombinieren. ** Methode1 und ** Methode2 sind private Methoden und Methode1 ruft Methode2 auf.

Singh Arun
quelle
4

Ich verstehe nicht wirklich, dass Sie die private Methode testen müssen. Das Hauptproblem besteht darin, dass Ihre öffentliche Methode als Rückgabetyp void hat und Sie daher Ihre öffentliche Methode nicht testen können. Daher sind Sie gezwungen, Ihre private Methode zu testen. Ist meine Vermutung richtig?

Einige mögliche Lösungen (AFAIK):

  1. Verspotten Sie Ihre privaten Methoden, aber Sie werden Ihre Methoden trotzdem nicht "tatsächlich" testen.

  2. Überprüfen Sie den Status des in der Methode verwendeten Objekts. Die meisten Methoden verarbeiten entweder die Eingabewerte und geben eine Ausgabe zurück oder ändern den Status der Objekte. Das Testen der Objekte auf den gewünschten Zustand kann ebenfalls verwendet werden.

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

    [Hier können Sie die private Methode testen, indem Sie die Statusänderung von classObj von null auf nicht null überprüfen.]

  3. Überarbeiten Sie Ihren Code ein wenig (Ich hoffe, dies ist kein Legacy-Code). Meine Grundlage beim Schreiben einer Methode ist, dass man immer etwas zurückgeben sollte (ein int / a boolean). Der zurückgegebene Wert KANN oder KANN NICHT von der Implementierung verwendet werden, wird aber SICHER vom Test verwendet

    Code.

    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
quelle
3

Es gibt tatsächlich eine Möglichkeit, Methoden von einem privaten Mitglied mit Mockito zu testen. Angenommen, Sie haben eine Klasse wie diese:

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

Wenn Sie testen möchten, a.methodwird eine Methode von aufgerufen SomeOtherClass, können Sie etwas wie unten schreiben.

@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(); wird das private Mitglied mit etwas stubben, das Sie ausspionieren können.

Fan Jin
quelle
2

Legen Sie Ihren Test in dasselbe Paket, aber in einen anderen Quellordner (src / main / java vs. src / test / java) und machen Sie diese Methoden paketprivat. Imo-Testbarkeit ist wichtiger als Datenschutz.

Roland Schneider
quelle
6
Der einzig legitime Grund besteht darin, einen Teil eines Legacy-Systems zu testen. Wenn Sie mit dem Testen der Methode private / package-private beginnen, legen Sie die Interna Ihres Objekts offen. Dies führt normalerweise zu einem schlechten überarbeitbaren Code. Bevorzugen Sie die Komposition, damit Sie Testbarkeit mit der Güte eines objektorientierten Systems erreichen können.
Brice
1
Einverstanden - das wäre der bevorzugte Weg. Wenn Sie jedoch wirklich private Methoden mit mockito testen möchten, ist dies die einzige (typsichere) Option, die Sie haben. Meine Antwort war allerdings etwas voreilig, ich hätte auf die Risiken hinweisen sollen, so wie Sie und die anderen es getan haben.
Roland Schneider
Dies ist mein bevorzugter Weg. Es ist nichts Falsches daran, ein internes Objekt auf paketprivater Ebene verfügbar zu machen. und Unit-Test ist White-Box-Test, müssen Sie die interne zum Testen kennen.
Andrew Feng
0

In Fällen, in denen die private Methode nicht ungültig ist und der Rückgabewert als Parameter für die Methode einer externen Abhängigkeit verwendet wird, können Sie die Abhängigkeit verspotten und mit a ArgumentCaptorden Rückgabewert erfassen. Beispielsweise:

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