Wie teste ich eine Klasse (mit xUnit) mit internen privaten Methoden, Feldern oder verschachtelten Klassen? Oder eine Funktion, die durch interne Verknüpfung ( static
in C / C ++) privat gemacht wird oder sich in einem privaten ( anonymen ) Namespace befindet?
Es scheint schlecht, den Zugriffsmodifikator für eine Methode oder Funktion zu ändern, um einen Test ausführen zu können.
java
unit-testing
tdd
Raedwald
quelle
quelle
Antworten:
Wenn Sie eine ältere Java- Anwendung haben und die Sichtbarkeit Ihrer Methoden nicht ändern dürfen, können Sie private Methoden am besten mit Reflection testen .
Intern verwenden wir Helfer, um / set
private
undprivate static
Variablen sowie Aufrufeprivate
undprivate static
Methoden abzurufen . Mit den folgenden Mustern können Sie so ziemlich alles tun, was mit den privaten Methoden und Feldern zu tun hat. Natürlich können Sieprivate static final
Variablen nicht durch Reflexion ändern .Und für Felder:
quelle
Der beste Weg, eine private Methode zu testen, ist eine andere öffentliche Methode. Wenn dies nicht möglich ist, ist eine der folgenden Bedingungen erfüllt:
quelle
Wenn ich private Methoden in einer Klasse habe, die so kompliziert sind, dass ich die privaten Methoden direkt testen muss, ist das ein Codegeruch: Meine Klasse ist zu kompliziert.
Mein üblicher Ansatz, um solche Probleme anzugehen, besteht darin, eine neue Klasse herauszuarbeiten, die die interessanten Teile enthält. Oft können diese Methode und die Felder, mit denen sie interagiert, und möglicherweise eine oder zwei andere Methoden in eine neue Klasse extrahiert werden.
Die neue Klasse macht diese Methoden als "öffentlich" verfügbar, sodass sie für Unit-Tests zugänglich sind. Die neuen und alten Klassen sind jetzt beide einfacher als die ursprüngliche Klasse, was für mich großartig ist (ich muss die Dinge einfach halten, sonst verliere ich mich!).
Beachten Sie, dass ich nicht vorschlage, dass Leute Klassen erstellen, ohne ihr Gehirn zu benutzen! Hier geht es darum, die Kräfte des Komponententests zu nutzen, um gute neue Klassen zu finden.
quelle
Ich habe in der Vergangenheit Reflexion verwendet , um dies für Java zu tun, und meiner Meinung nach war es ein großer Fehler.
Genau genommen sollten Sie keine Komponententests schreiben, die private Methoden direkt testen. Was Sie testen sollten , ist der öffentliche Vertrag, den die Klasse mit anderen Objekten hat. Sie sollten niemals die Interna eines Objekts direkt testen. Wenn ein anderer Entwickler eine kleine interne Änderung an der Klasse vornehmen möchte, die sich nicht auf den öffentlichen Vertrag der Klasse auswirkt, muss er Ihren reflexionsbasierten Test ändern, um sicherzustellen, dass er funktioniert. Wenn Sie dies während eines Projekts wiederholt tun, stellen Unit-Tests keine nützliche Messung des Codezustands mehr dar und werden zu einem Hindernis für die Entwicklung und zu einem Ärger für das Entwicklungsteam.
Ich empfehle stattdessen, ein Tool zur Codeabdeckung wie Cobertura zu verwenden, um sicherzustellen, dass die von Ihnen geschriebenen Komponententests eine angemessene Abdeckung des Codes in privaten Methoden bieten. Auf diese Weise testen Sie indirekt, was die privaten Methoden tun, und behalten ein höheres Maß an Agilität bei.
quelle
In diesem Artikel: Testen privater Methoden mit JUnit und SuiteRunner (Bill Venners) haben Sie grundsätzlich 4 Optionen:
quelle
Im Allgemeinen soll ein Komponententest die öffentliche Schnittstelle einer Klasse oder Einheit ausüben. Private Methoden sind daher Implementierungsdetails, die Sie nicht explizit testen würden.
quelle
Nur zwei Beispiele, wo ich eine private Methode testen möchte:
SecurityManager
nicht konfiguriert ist, um dies zu verhindern).Ich verstehe die Idee, nur den "Vertrag" zu testen. Aber ich sehe nicht, dass man befürworten kann, Code nicht zu testen - Ihr Kilometerstand kann variieren.
Mein Kompromiss besteht also darin, die JUnits durch Reflexion zu komplizieren, anstatt meine Sicherheit und mein SDK zu gefährden.
quelle
private
ist dies nicht die einzige Alternative. Kein Zugriffsmodifikator istpackage private
und bedeutet, dass Sie ihn einem Unit-Test unterziehen können, solange sich Ihr Unit-Test im selben Paket befindet.Die privaten Methoden werden von einer öffentlichen Methode aufgerufen, daher sollten die Eingaben in Ihre öffentlichen Methoden auch private Methoden testen, die von diesen öffentlichen Methoden aufgerufen werden. Wenn eine öffentliche Methode fehlschlägt, kann dies ein Fehler in der privaten Methode sein.
quelle
Ein anderer Ansatz, den ich verwendet habe, besteht darin, eine private Methode so zu ändern, dass sie privat oder geschützt verpackt wird, und sie dann mit der Annotation @VisibleForTesting der Google Guava-Bibliothek zu ergänzen .
Dies weist jeden, der diese Methode verwendet, an, Vorsicht walten zu lassen und nicht einmal in einem Paket direkt darauf zuzugreifen. Auch eine Testklasse muss nicht in demselben Paket sein physisch , aber im selben Paket unter dem Testordner.
Befindet sich beispielsweise eine zu testende Methode, sollte
src/main/java/mypackage/MyClass.java
Ihr Testaufruf in platziert werdensrc/test/java/mypackage/MyClassTest.java
. Auf diese Weise haben Sie Zugriff auf die Testmethode in Ihrer Testklasse.quelle
Um Test Legacy - Code mit großen und schrulligen Klassen, ist es oft sehr hilfreich, wenn die eine Privat zu testen (oder öffentlich) Methode Ich schreibe jetzt .
Ich benutze das junitx.util.PrivateAccessor-Paket für Java. Viele hilfreiche Einzeiler für den Zugriff auf private Methoden und private Felder.
quelle
Class
, dass Sie nur aufstatic
Mitglieder zugreifen , wenn Sie in diesen Methoden einen als ersten Parameter verwenden .Im Spring Framework können Sie private Methoden mit folgender Methode testen:
Zum Beispiel:
quelle
Nachdem ich die Lösung von Cem Catikkas mit Reflection für Java ausprobiert habe , muss ich sagen, dass seine Lösung eleganter war als hier beschrieben. Wenn Sie jedoch nach einer Alternative zur Verwendung von Reflection suchen und Zugriff auf die zu testende Quelle haben, ist dies weiterhin eine Option.
Es ist möglich, private Methoden einer Klasse zu testen , insbesondere bei testgetriebener Entwicklung , bei der Sie kleine Tests entwerfen möchten, bevor Sie Code schreiben.
Durch das Erstellen eines Tests mit Zugriff auf private Mitglieder und Methoden können Codebereiche getestet werden, auf die nur schwer zugegriffen werden kann, wenn nur auf öffentliche Methoden zugegriffen wird. Wenn eine öffentliche Methode mehrere Schritte umfasst, kann sie aus mehreren privaten Methoden bestehen, die dann einzeln getestet werden können.
Vorteile:
Nachteile:
Wenn kontinuierliche Tests diese Methode erfordern, kann dies ein Signal dafür sein, dass die privaten Methoden extrahiert werden sollten, die auf herkömmliche öffentliche Weise getestet werden könnten.
Hier ist ein kompliziertes Beispiel dafür, wie dies funktionieren würde:
Die innere Klasse würde zusammengestellt werden
ClassToTest$StaticInnerTest
.Siehe auch: Java-Tipp 106: Statische innere Klassen für Spaß und Gewinn
quelle
Wie andere gesagt haben ... testen Sie private Methoden nicht direkt. Hier einige Gedanken:
Führen Sie die Codeabdeckung für die Komponententests aus. Wenn Sie feststellen, dass die Methoden nicht vollständig getestet wurden, fügen Sie sie zu den Tests hinzu, um die Abdeckung zu verbessern. Streben Sie eine 100% ige Codeabdeckung an, aber stellen Sie fest, dass Sie diese wahrscheinlich nicht erhalten werden.
quelle
Private Methoden werden von öffentlichen genutzt. Ansonsten sind sie toter Code. Aus diesem Grund testen Sie die öffentliche Methode, indem Sie die erwarteten Ergebnisse der öffentlichen Methode und damit die von ihr verwendeten privaten Methoden bestätigen.
Das Testen privater Methoden sollte durch Debuggen getestet werden, bevor Sie Ihre Komponententests mit öffentlichen Methoden ausführen.
Sie können auch mithilfe einer testgetriebenen Entwicklung debuggt werden, indem Sie Ihre Komponententests debuggen, bis alle Ihre Aussagen erfüllt sind.
Ich persönlich glaube, es ist besser, Klassen mit TDD zu erstellen. Erstellen Sie die öffentlichen Methodenstubs und generieren Sie dann Komponententests mit allen im Voraus definierten Zusicherungen, damit das erwartete Ergebnis der Methode ermittelt wird, bevor Sie sie codieren. Auf diese Weise gehen Sie nicht den falschen Weg, um die Aussagen zum Komponententest an die Ergebnisse anzupassen. Ihre Klasse ist dann robust und erfüllt die Anforderungen, wenn alle Ihre Unit-Tests bestanden sind.
quelle
Wenn Sie Spring verwenden, bietet ReflectionTestUtils einige nützliche Tools, die hier mit minimalem Aufwand helfen. Zum Beispiel, um ein Mock für ein privates Mitglied einzurichten, ohne gezwungen zu sein, einen unerwünschten öffentlichen Setter hinzuzufügen:
quelle
Wenn Sie versuchen, vorhandenen Code zu testen, den Sie nur ungern oder nicht ändern können, ist Reflection eine gute Wahl.
Wenn das Design der Klasse noch flexibel ist und Sie eine komplizierte private Methode haben, die Sie separat testen möchten, empfehlen wir Ihnen, sie in eine separate Klasse zu ziehen und diese Klasse separat zu testen. Dies muss die öffentliche Schnittstelle der ursprünglichen Klasse nicht ändern. Es kann intern eine Instanz der Hilfsklasse erstellen und die Hilfsmethode aufrufen.
Wenn Sie schwierige Fehlerbedingungen testen möchten, die von der Hilfsmethode herrühren, können Sie noch einen Schritt weiter gehen. Extrahieren Sie eine Schnittstelle aus der Hilfsklasse, fügen Sie der ursprünglichen Klasse einen öffentlichen Getter und Setter hinzu, um die Hilfsklasse (die über ihre Schnittstelle verwendet wird) einzufügen, und fügen Sie dann eine Scheinversion der Hilfsklasse in die ursprüngliche Klasse ein, um zu testen, wie die ursprüngliche Klasse funktioniert reagiert auf Ausnahmen vom Helfer. Dieser Ansatz ist auch hilfreich, wenn Sie die ursprüngliche Klasse testen möchten, ohne auch die Hilfsklasse zu testen.
quelle
Durch das Testen privater Methoden wird die Kapselung Ihrer Klasse unterbrochen, da bei jeder Änderung der internen Implementierung der Clientcode (in diesem Fall die Tests) unterbrochen wird.
Testen Sie also keine privaten Methoden.
quelle
Die Antwort von der FAQ-Seite von JUnit.org :
PS Ich bin der Hauptverantwortliche für Dp4j . Frag mich, ob du Hilfe brauchst. :) :)
quelle
Wenn Sie private Methoden einer Legacy-Anwendung testen möchten, bei denen Sie den Code nicht ändern können, ist eine Option für Java jMockit , mit der Sie Mocks für ein Objekt erstellen können, auch wenn diese für die Klasse privat sind.
quelle
Deencapsulation.invoke(instance, "privateMethod", param1, param2);
Ich neige dazu, private Methoden nicht zu testen. Da liegt der Wahnsinn. Persönlich glaube ich, dass Sie nur Ihre öffentlich zugänglichen Schnittstellen testen sollten (und das schließt geschützte und interne Methoden ein).
quelle
Wenn Sie JUnit verwenden, schauen Sie sich Junit-Addons an . Es kann das Java-Sicherheitsmodell ignorieren und auf private Methoden und Attribute zugreifen.
quelle
Ich würde vorschlagen, dass Sie Ihren Code ein wenig umgestalten. Wenn Sie darüber nachdenken müssen, Reflektion oder andere Dinge zu verwenden, um nur Ihren Code zu testen, läuft etwas mit Ihrem Code nicht.
Sie haben verschiedene Arten von Problemen erwähnt. Beginnen wir mit privaten Feldern. Bei privaten Feldern hätte ich einen neuen Konstruktor hinzugefügt und Felder eingefügt. An Stelle von:
Ich hätte das benutzt:
Dies ist auch mit altem Code kein Problem. Alter Code verwendet einen leeren Konstruktor, und wenn Sie mich fragen, sieht überarbeiteter Code sauberer aus, und Sie können die erforderlichen Werte ohne Reflexion in den Test einfügen.
Nun zu privaten Methoden. Nach meiner persönlichen Erfahrung hat diese Methode in dieser Klasse nichts zu tun, wenn Sie eine private Methode zum Testen stubben müssen. In diesem Fall besteht ein gängiges Muster darin, es in eine Schnittstelle zu verpacken ,
Callable
und dann übergeben Sie diese Schnittstelle auch im Konstruktor (mit diesem Trick mit mehreren Konstruktoren):Meistens sieht alles, was ich geschrieben habe, so aus, als wäre es ein Abhängigkeitsinjektionsmuster. Nach meiner persönlichen Erfahrung ist es beim Testen sehr nützlich, und ich denke, dass diese Art von Code sauberer und einfacher zu warten ist. Ich würde dasselbe über verschachtelte Klassen sagen. Wenn eine verschachtelte Klasse eine umfangreiche Logik enthält, ist es besser, wenn Sie sie als private Paketklasse verschoben und in eine Klasse eingefügt haben, die sie benötigt.
Es gibt auch einige andere Entwurfsmuster, die ich beim Umgestalten und Verwalten von Legacy-Code verwendet habe, aber alles hängt von den Fällen ab, in denen Ihr Code getestet werden soll. Die Verwendung von Reflection ist meistens kein Problem, aber wenn Sie eine Unternehmensanwendung haben, die stark getestet wird und Tests vor jeder Bereitstellung ausgeführt werden, wird alles sehr langsam (es ist nur ärgerlich und ich mag solche Dinge nicht).
Es gibt auch eine Setter-Injektion, aber ich würde nicht empfehlen, sie zu verwenden. Ich bleibe lieber bei einem Konstruktor und initialisiere alles, wenn es wirklich notwendig ist, und lasse die Möglichkeit, notwendige Abhängigkeiten einzufügen.
quelle
ClassToTest(Callable)
Injektion. Das machtClassToTest
es komplizierter. Halte es einfach. Dazu muss dann jemand anderesClassToTest
etwas sagen ,ClassToTest
dessen Chef sein sollte. Es gibt einen Platz zum Einfügen von Logik, aber das ist es nicht. Sie haben es gerade schwieriger gemacht, die Klasse zu pflegen, nicht einfacher.X
dieX
Komplexität nicht bis zu dem Punkt erhöht, an dem sie möglicherweise mehr Probleme verursacht ... und daher zusätzliche Tests erfordert ... was, wenn Sie auf eine Weise implementiert haben, die mehr Probleme verursachen kann. (Dies ist keine Endlosschleife; jede Iteration ist wahrscheinlich kleiner als dieClassToTest
schwieriger wird, den Unterricht aufrechtzuerhalten? Tatsächlich erleichtert dies die Wartung Ihrer Anwendung. Was schlagen Sie vor, um eine neue Klasse für jeden anderen Wert zu erstellen, den Sie infirst
und für 'zweite' Variablen benötigen ?class A{ A(){} f(){} }
ist einfacher alsclass A { Logic f; A(logic_f) } class B { g() { new A( logic_f) } }
. Wenn dies nicht wahr wäre und wenn es wahr wäre, dass die Bereitstellung einer Klassenlogik als Konstruktorargumente einfacher zu pflegen wäre, würden wir die gesamte Logik als Konstruktorargumente übergeben. Ich verstehe nur nicht, wie Sie behaupten könnenclass B{ g() { new A( you_dont_know_your_own_logic_but_I_do ) } }
, dass es einfacher ist, A zu warten. Es gibt Fälle, in denen eine solche Injektion sinnvoll ist, aber ich sehe Ihr Beispiel nicht als "einfacher zu pflegen"Auf eine private Methode darf nur innerhalb derselben Klasse zugegriffen werden. Es gibt also keine Möglichkeit, eine "private" Methode einer Zielklasse aus einer Testklasse heraus zu testen. Ein Ausweg ist, dass Sie Unit-Tests manuell durchführen oder Ihre Methode von "privat" in "geschützt" ändern können.
Und dann kann auf eine geschützte Methode nur innerhalb desselben Pakets zugegriffen werden, in dem die Klasse definiert ist. Wenn Sie also eine geschützte Methode einer Zielklasse testen, müssen Sie Ihre Testklasse im selben Paket wie die Zielklasse definieren.
Wenn all das nicht Ihren Anforderungen entspricht, verwenden Sie die Reflektionsmethode, um auf die private Methode zuzugreifen.
quelle
Wie viele oben vorgeschlagen haben, besteht eine gute Möglichkeit darin, sie über Ihre öffentlichen Schnittstellen zu testen.
Wenn Sie dies tun, ist es eine gute Idee, ein Tool zur Codeabdeckung (wie Emma) zu verwenden, um festzustellen, ob Ihre privaten Methoden tatsächlich aus Ihren Tests ausgeführt werden.
quelle
Hier ist meine generische Funktion zum Testen privater Felder:
quelle
Ein Beispiel finden Sie weiter unten.
Die folgende Importanweisung sollte hinzugefügt werden:
Jetzt können Sie das Objekt mit der privaten Methode, dem aufzurufenden Methodennamen und den folgenden zusätzlichen Parametern direkt übergeben.
quelle
Heute habe ich eine Java-Bibliothek gepusht, um beim Testen privater Methoden und Felder zu helfen. Es wurde speziell für Android entwickelt, kann aber wirklich für jedes Java-Projekt verwendet werden.
Wenn Sie Code mit privaten Methoden oder Feldern oder Konstruktoren haben, können Sie BoundBox verwenden . Es macht genau das, wonach Sie suchen. Hier ist ein Beispiel für einen Test, der auf zwei private Felder einer Android-Aktivität zugreift, um sie zu testen:
BoundBox erleichtert das Testen von privaten / geschützten Feldern, Methoden und Konstruktoren. Sie können sogar auf Dinge zugreifen, die durch Vererbung verborgen sind. In der Tat unterbricht BoundBox die Kapselung. Sie erhalten Zugriff auf all das durch Reflexion, ABER alles wird zur Kompilierungszeit überprüft.
Es ist ideal zum Testen von Legacy-Code. Verwenden Sie es vorsichtig. ;)
https://github.com/stephanenicolas/boundbox
quelle
Zuerst werde ich diese Frage wegwerfen: Warum brauchen Ihre privaten Mitglieder isolierte Tests? Sind sie so komplex und bieten so komplizierte Verhaltensweisen, dass neben der öffentlichen Oberfläche auch Tests erforderlich sind? Es handelt sich um Unit-Tests, nicht um Code-Code-Tests. Schwitzen Sie nicht die kleinen Sachen.
Wenn sie so groß sind, dass diese privaten Mitglieder jeweils eine komplexe Einheit bilden, sollten Sie erwägen, solche privaten Mitglieder aus dieser Klasse heraus zu überarbeiten.
Wenn Refactoring unangemessen oder nicht durchführbar ist, können Sie das Strategiemuster verwenden, um den Zugriff auf diese privaten Mitgliedsfunktionen / Mitgliedsklassen im Unit-Test zu ersetzen? Im Unit-Test würde die Strategie eine zusätzliche Validierung bieten, in Release-Builds wäre dies jedoch ein einfaches Passthrough.
quelle
Ich hatte kürzlich dieses Problem und schrieb ein kleines Tool namens Picklock , das die Probleme der expliziten Verwendung der Java Reflection API vermeidet. Zwei Beispiele:
Aufrufmethoden, zB
private void method(String s)
- durch Java-Reflektionprivate void method(String s)
Aufrufmethoden , zB - von PicklockEinstellen von Feldern, zB
private BigInteger amount;
- durch Java-ReflexionEinstellen von Feldern, zB
private BigInteger amount;
- durch Picklockquelle
PowerMockito ist dafür gemacht. Verwenden Sie die Maven-Abhängigkeit
Dann können Sie tun
quelle