Wie testest du private Methoden?

184

Ich arbeite an einem Java-Projekt. Ich bin neu in Unit-Tests. Was ist der beste Weg, um private Methoden in Java-Klassen einem Komponententest zu unterziehen?

Vinoth Kumar CM
quelle
1
Überprüfen Sie diese Frage auf StackOverflow. Einige Techniken werden erwähnt und diskutiert. Was ist der beste Weg, um private Methoden zu testen?
Chiron
34
Meine Meinung war schon immer, dass private Methoden nicht getestet werden müssen, da Sie testen sollten, was verfügbar ist. Eine öffentliche Methode. Wenn Sie die öffentliche Methode nicht brechen können, ist es dann wirklich wichtig, was die privaten Methoden tun?
Rig
1
Sowohl öffentliche als auch private Methoden sollten getestet werden. Daher muss ein Testfahrer im Allgemeinen in der Klasse sein, die er testet. wie diese .
Überaustausch
2
@Rig - +1 - Sie sollten in der Lage sein, das gesamte erforderliche Verhalten einer privaten Methode von Ihren öffentlichen Methoden aus aufzurufen. Wenn dies nicht möglich ist, kann die Funktionalität ohnehin nie aufgerufen werden, sodass es keinen Sinn macht, sie zu testen.
James Anderson
Verwenden Sie @Jailbreakdas Manifold- Framework, um direkt auf private Methoden zuzugreifen. Auf diese Weise bleibt Ihr Testcode typsicher und lesbar. Vor allem keine Designkompromisse, keine Überbelichtung von Methoden und Feldern für Tests.
Scott

Antworten:

239

Private Methoden werden im Allgemeinen nicht direkt in einem Komponententest getestet. Betrachten Sie sie als Implementierungsdetail, da sie privat sind. Niemand wird jemals einen von ihnen anrufen und erwarten, dass es auf eine bestimmte Art und Weise funktioniert.

Sie sollten stattdessen Ihre öffentliche Schnittstelle testen. Wenn die Methoden, die Ihre privaten Methoden aufrufen, erwartungsgemäß funktionieren, gehen Sie davon aus, dass Ihre privaten Methoden ordnungsgemäß funktionieren.

Adam Lear
quelle
39
+1 Milliarde. Und wenn eine private Methode niemals aufgerufen wird, testen Sie sie nicht in einer Einheit, sondern löschen Sie sie!
Steven A. Lowe
246
Ich stimme dir nicht zu. Manchmal ist eine private Methode nur ein Implementierungsdetail, aber sie ist immer noch so komplex, dass sie getestet werden muss, um sicherzustellen, dass sie richtig funktioniert. Die öffentliche Schnittstelle bietet möglicherweise einen zu hohen Abstraktionsgrad, um einen Test zu schreiben, der direkt auf diesen bestimmten Algorithmus abzielt. Aufgrund der gemeinsam genutzten Daten ist es nicht immer möglich, diese in eine separate Klasse zu zerlegen. In diesem Fall würde ich sagen, dass es in Ordnung ist, eine private Methode zu testen.
quant_dev
10
Natürlich löschen. Der einzige mögliche Grund, eine Funktion, die Sie nicht verwenden, nicht zu löschen, ist, wenn Sie glauben, dass Sie sie in Zukunft benötigen. Sie sollten sie auch dann löschen, aber die Funktion in Ihren Festschreibungsprotokollen notieren oder sie zumindest auskommentieren Die Leute wissen, dass es zusätzliche Dinge sind, die sie ignorieren können.
jhocking
38
@quant_dev: Manchmal muss eine Klasse in mehrere andere Klassen umgestaltet werden. Ihre freigegebenen Daten können auch in eine separate Klasse gezogen werden. Sagen wir, eine "Kontext" -Klasse. Dann können Ihre beiden neuen Klassen auf die Kontextklasse für ihre gemeinsam genutzten Daten verweisen. Dies mag unvernünftig erscheinen, aber wenn Ihre privaten Methoden komplex genug sind, um individuelle Tests zu erfordern, ist dies ein Codegeruch, der darauf hinweist, dass Ihr Objektdiagramm ein wenig detaillierter werden muss.
Phil
43
Diese Antwort beantwortet NICHT die Frage. Die Frage ist, wie private Methoden getestet werden sollen und nicht, ob sie getestet werden sollen. Ob eine private Methode Unit-getestet werden soll, ist eine interessante Frage, die es wert ist, diskutiert zu werden, aber nicht hier . Als angemessene Antwort fügen Sie der Frage einen Kommentar hinzu, der besagt, dass das Testen privater Methoden möglicherweise keine gute Idee ist, und geben Sie einen Link zu einer separaten Frage an, die sich eingehender mit der Frage befasst.
TallGuy
118

Im Allgemeinen würde ich es vermeiden. Wenn Ihre private Methode so komplex ist, dass ein separater Komponententest erforderlich ist, hat sie häufig eine eigene Klasse verdient. Dies kann Sie dazu ermutigen, es in einer Weise zu schreiben, die wiederverwendbar ist. Sie sollten dann die neue Klasse testen und die öffentliche Schnittstelle in Ihrer alten Klasse aufrufen.

Andererseits führt das Ausklammern der Implementierungsdetails in separate Klassen manchmal zu Klassen mit komplexen Schnittstellen, vielen Daten, die zwischen der alten und der neuen Klasse übertragen werden, oder zu einem Entwurf, der aus OOP-Sicht gut aussieht, dies aber nicht tut Passen Sie die Intuitionen der Problemdomäne an (z. B. ist das Aufteilen eines Preismodells in zwei Teile, um das Testen privater Methoden zu vermeiden, nicht sehr intuitiv und kann später zu Problemen bei der Pflege / Erweiterung des Codes führen). Sie möchten keine "Zwillingsklassen" haben, die immer zusammen gewechselt werden.

Wenn ich mich zwischen Kapselung und Testbarkeit entscheiden muss, würde ich mich lieber für die zweite Variante entscheiden. Es ist wichtiger, den richtigen Code zu haben (dh die richtige Ausgabe zu produzieren) als ein schönes OOP-Design, das nicht richtig funktioniert, weil es nicht ausreichend getestet wurde. In Java können Sie einfach der Methode "default" Zugriff gewähren und den Komponententest im selben Paket ablegen. Komponententests sind einfach Teil des Pakets, das Sie entwickeln, und es ist in Ordnung, eine Abhängigkeit zwischen den Tests und dem Code zu haben, der getestet wird. Wenn Sie die Implementierung ändern, müssen Sie möglicherweise Ihre Tests ändern, aber das ist in Ordnung. Jede Änderung der Implementierung erfordert ein erneutes Testen des Codes. Wenn die Tests dazu geändert werden müssen, tun Sie dies einfach es.

Im Allgemeinen kann eine Klasse mehr als eine Schnittstelle anbieten. Es gibt eine Schnittstelle für die Benutzer und eine Schnittstelle für die Betreuer. Die zweite Option kann mehr anzeigen, um sicherzustellen, dass der Code angemessen getestet wird. Es muss sich nicht um einen Komponententest für eine private Methode handeln, sondern es kann sich beispielsweise um eine Protokollierung handeln. Die Protokollierung bricht auch die Kapselung ab, aber wir tun es trotzdem, weil es so nützlich ist.

quant_dev
quelle
9
+1 Interessanter Punkt. Am Ende geht es um Wartbarkeit, nicht um die Einhaltung der "Regeln" eines guten OOP.
Phil
9
+1 Ich stimme der Testbarkeit der Verkapselung voll und ganz zu.
Richard
31

Das Testen privater Methoden würde von ihrer Komplexität abhängen. Einige einzeilige private Methoden würden den zusätzlichen Testaufwand nicht wirklich rechtfertigen (dies kann auch für öffentliche Methoden gelten), aber einige private Methoden können genauso komplex sein wie öffentliche Methoden und über die öffentliche Schnittstelle nur schwer zu testen sein.

Meine bevorzugte Technik ist es, das private Methodenpaket privat zu machen, wodurch der Zugriff auf einen Komponententest im selben Paket ermöglicht wird, der jedoch weiterhin aus allen anderen Codes eingekapselt wird. Dies bietet den Vorteil, dass die private Methodenlogik direkt getestet werden kann, anstatt sich auf einen öffentlichen Methodentest verlassen zu müssen, um alle Teile der (möglicherweise) komplexen Logik abzudecken.

Wenn dies mit der Anmerkung @VisibleForTesting in der Google Guava-Bibliothek gepaart ist, kennzeichnen Sie diese private Methode des Pakets eindeutig als nur zum Testen sichtbar und sollten sie daher nicht von anderen Klassen aufgerufen werden.

Gegner dieser Technik argumentieren, dass dies die Kapselung unterbrechen und private Methoden öffnen wird, um im selben Paket zu codieren. Ich bin damit einverstanden, dass dies die Kapselung unterbricht und privaten Code für andere Klassen öffnet. Ich behaupte jedoch, dass das Testen komplexer Logik wichtiger ist als strikte Kapselung. Die Verwendung von nicht als sichtbar gekennzeichneten privaten Methoden für das Testen muss jedoch in der Verantwortung der Entwickler liegen Verwenden und Ändern der Codebasis.

Private Methode vor dem Testen:

private int add(int a, int b){
    return a + b;
}

Paket private Methode zum Testen bereit:

@VisibleForTesting
int add(int a, int b){
    return a + b;
}

Hinweis: Das Einfügen von Tests in dasselbe Paket entspricht nicht dem Einfügen in denselben physischen Ordner. Das Trennen des Hauptcodes und des Testcodes in separate physische Ordnerstrukturen ist im Allgemeinen eine gute Praxis, aber diese Technik funktioniert, solange die Klassen wie im selben Paket definiert sind.

Richard
quelle
14

Wenn Sie keine externen APIs verwenden können oder möchten, können Sie dennoch die reine Standard-JDK-API verwenden, um mithilfe von Reflection auf private Methoden zuzugreifen. Hier ist ein Beispiel

MyObject obj = new MyObject();
Method privateMethod = MyObject.class.getDeclaredMethod("getFoo", null);
privateMethod.setAccessible(true);
String returnValue = (String) privateMethod.invoke(obj, null);
System.out.println("returnValue = " + returnValue);

Überprüfen Sie das Java-Tutorial http://docs.oracle.com/javase/tutorial/reflect/ oder die Java-API http://docs.oracle.com/javase/7/docs/api/java/lang/reflect/package-summary. HTML für weitere Informationen.

Wie @kij in seiner Antwort zitiert hat, gibt es Zeiten, in denen eine einfache Lösung mit Reflektion wirklich gut ist, um eine private Methode zu testen.

Gustavo Coelho
quelle
9

Einheitentestfall bedeutet das Testen der Codeeinheit. Dies bedeutet nicht, dass Sie die Schnittstelle testen. Wenn Sie die Schnittstelle testen, bedeutet dies nicht, dass Sie die Codeeinheit testen. Es wird zu einer Art Black-Box-Test. Außerdem ist es besser, Probleme auf der Ebene der kleinsten Einheit zu finden, als die Probleme auf der Schnittstellenebene zu ermitteln und dann zu versuchen, das zu debuggen, was nicht funktioniert hat. Daher sollte der Einheitentestfall unabhängig von ihrem Umfang getestet werden. Das Folgende ist eine Möglichkeit, private Methoden zu testen.

Wenn Sie Java verwenden, können Sie mit jmockit, das Deencapsulation.invoke bereitstellt, eine beliebige private Methode der zu testenden Klasse aufrufen. Es verwendet Reflektion, um es schließlich aufzurufen, bietet aber einen schönen Wrapper um es. ( https://code.google.com/p/jmockit/ )

agrawalankur
quelle
Wie beantwortet dies die gestellte Frage?
Mücke
@gnat, Die Frage war, was der beste Weg ist, um private Methoden in Java-Klassen zu testen? Und mein erster Punkt beantwortet die Frage.
Agrawalankur
Ihr erster Punkt wirbt lediglich für ein Tool, erklärt aber nicht, warum Sie es für besser halten als Alternativen, wie z. B. das indirekte Testen privater Methoden, wie in der Antwort oben vorgeschlagen und erläutert
gnat
jmockit ist umgezogen und die alte offizielle Seite verweist auf einen Artikel über "Synthetic Urine and Artificial Pee". Was übrigens immer noch mit Spott zu tun hat, aber hier nicht relevant ist :) Die neue offizielle Seite ist: jmockit.github.io
Guillaume
8

Zuallererst, wie andere Autoren vorgeschlagen haben: Überlegen Sie zweimal, ob Sie die private Methode wirklich testen müssen. Und wenn, ...

In .NET können Sie es in die Methode "Internal" konvertieren und das Paket " InternalVisible " für Ihr Unit-Test-Projekt erstellen .

In Java können Sie Tests selbst in die zu testende Klasse schreiben und Ihre Testmethoden sollten auch private Methoden aufrufen können. Ich habe keine große Java-Erfahrung, daher ist dies wahrscheinlich nicht die beste Vorgehensweise.

Vielen Dank.

Budda
quelle
7

Normalerweise mache ich solche Methoden geschützt. Angenommen, Ihre Klasse befindet sich in:

src/main/java/you/Class.java

Sie können eine Testklasse erstellen als:

src/test/java/you/TestClass.java

Jetzt haben Sie Zugriff auf geschützte Methoden und können diese Unit testen (JUnit oder TestNG spielen keine Rolle), aber Sie behalten diese Methoden vor Anrufern, die Sie nicht wollten.

Beachten Sie, dass dies einen Quellenbaum im Maven-Stil erwartet.

Gyorgyabraham
quelle
3

Wenn Sie wirklich private Methoden testen müssen, können Sie mit Java fest assertund / oder fest reflect. Es benutzt Reflexion.

Importieren Sie die Bibliothek mit maven (bestimmte Versionen sind meiner Meinung nach nicht die neuesten) oder importieren Sie sie direkt in Ihren Klassenpfad:

<dependency>
     <groupId>org.easytesting</groupId>
     <artifactId>fest-assert</artifactId>
     <version>1.4</version>
    </dependency>

    <dependency>
     <groupId>org.easytesting</groupId>
     <artifactId>fest-reflect</artifactId>
    <version>1.2</version>
</dependency>

Wenn Sie beispielsweise eine Klasse mit dem Namen "MyClass" und einer privaten Methode mit dem Namen "myPrivateMethod" haben, die einen String als Parameter verwendet und dessen Wert auf "Dies ist ein cooler Test!" Aktualisiert, können Sie den folgenden Junit-Test durchführen:

import static org.fest.reflect.core.Reflection.method;
...

MyClass objectToTest;

@Before
public void setUp(){
   objectToTest = new MyClass();
}

@Test
public void testPrivateMethod(){
   // GIVEN
   String myOriginalString = "toto";
   // WHEN
   method("myPrivateMethod").withParameterTypes(String.class).in(objectToTest).invoke(myOriginalString);
   // THEN
   Assert.assertEquals("this is cool testing !", myOriginalString);
}

Mit dieser Bibliothek können Sie auch alle Bean-Eigenschaften (unabhängig davon, ob sie privat sind oder keine Setter geschrieben wurden) durch einen Mock ersetzen. Die Verwendung mit Mockito oder einem anderen Mock-Framework ist wirklich cool. Das einzige, was Sie im Moment wissen müssen (nicht wissen, ob dies in den nächsten Versionen besser sein wird), ist der Name des Zielfelds / der Zielmethode, die Sie manipulieren möchten, und dessen Signatur.

kij
quelle
2
Durch Reflektion können Sie zwar private Methoden testen, sie eignen sich jedoch nicht zum Erkennen von Verwendungszwecken. Wenn Sie den Namen einer privaten Methode ändern, wird der Test dadurch unterbrochen.
Richard
1
Der Test bricht ja, aber dies ist aus meiner Sicht ein kleines Problem. Natürlich stimme ich Ihrer Erklärung zur Sichtbarkeit des Pakets (mit Testklassen in separaten Ordnern, aber mit denselben Paketen) voll und ganz zu, aber manchmal auch nicht Ich habe wirklich die Wahl. Zum Beispiel, wenn Sie eine sehr kurze Mission in einem Unternehmen haben, das keine "guten" Methoden anwendet (Testklassen nicht im selben Paket usw.), wenn Sie keine Zeit haben, vorhandenen Code, an dem Sie arbeiten, umzugestalten / Testing, das ist immer noch eine gute Alternative.
kij
2

Was ich normalerweise in C # mache, ist, meine Methoden geschützt und nicht privat zu machen. Es ist ein etwas weniger privater Zugriffsmodifikator, verbirgt jedoch die Methode vor allen Klassen, die nicht von der getesteten Klasse erben.

public class classUnderTest
{
   //this would normally be private
   protected void methodToTest()
   {
     //some code here
   }
}

Jede Klasse, die nicht direkt von classUnderTest erbt, hat keine Ahnung, dass methodToTest überhaupt existiert. In meinem Testcode kann ich eine spezielle Testklasse erstellen, die den Zugriff auf diese Methode erweitert und ermöglicht ...

class TestingClass : classUnderTest
{
   public void methodToTest()
   {
     //this returns the method i would like to test
     return base.methodToTest();
   }
}

Diese Klasse existiert nur in meinem Testprojekt. Ihr einziger Zweck besteht darin, den Zugriff auf diese einzelne Methode zu ermöglichen. Damit kann ich auf Orte zugreifen, die die meisten anderen Klassen nicht haben.

Raumfahrer
quelle
4
protectedist Teil der öffentlichen API und unterliegt denselben Einschränkungen (kann niemals geändert werden, muss dokumentiert werden, ...). In C # können Sie internalzusammen mit InternalsVisibleTo.
CodesInChaos
1

Sie können private Methoden einfach testen, wenn Sie Ihre Komponententests in eine innere Klasse der Klasse einfügen, die Sie testen. Wenn Sie TestNG verwenden, müssen Ihre Komponententests öffentliche statische innere Klassen sein, die mit @Test wie folgt kommentiert sind:

@Test
public static class UserEditorTest {
    public void test_private_method() {
       assertEquals(new UserEditor().somePrivateMethod(), "success");
    }
}

Da es sich um eine innere Klasse handelt, kann die private Methode aufgerufen werden.

Meine Tests werden von maven ausgeführt und diese Testfälle werden automatisch gefunden. Wenn Sie nur eine Klasse testen möchten, können Sie dies tun

$ mvn test -Dtest=*UserEditorTest

Quelle: http://www.ninthavenue.com.au/how-to-unit-test-private-methods

Roger Keays
quelle
4
Sie schlagen vor, Testcode (und zusätzliche innere Klassen) in den tatsächlichen Code des bereitgestellten Pakets aufzunehmen?
1
Die Testklasse ist eine innere Klasse der Klasse, die Sie testen möchten. Wenn Sie nicht möchten, dass diese inneren Klassen implementiert werden, löschen Sie einfach die * Test.class-Dateien aus Ihrem Paket.
Roger Keays
1
Ich mag diese Option nicht, aber für viele ist sie wahrscheinlich eine gültige Lösung, die die Abstriche nicht wert ist.
Bill K
@ RogerKeays: Technisch gesehen, wenn Sie eine Bereitstellung durchführen, wie entfernen Sie solche "inneren Testklassen"? Bitte sag nicht, dass du sie von Hand geschnitten hast.
Gyorgyabraham
Ich entferne sie nicht. Ja. Ich stelle Code bereit, der zur Laufzeit nie ausgeführt wird. Es ist wirklich so schlimm.
Roger Keays