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?
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?
Antworten:
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.
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.
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.
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.
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/ )
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.
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.
Wenn Sie wirklich private Methoden testen müssen, können Sie mit Java fest assert
und / 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.
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.
protected
ist Teil der öffentlichen API und unterliegt denselben Einschränkungen (kann niemals geändert werden, muss dokumentiert werden, ...). In C # können Sie internal
zusammen mit InternalsVisibleTo
.
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