Wie teste ich eine private Funktion oder eine Klasse mit privaten Methoden, Feldern oder inneren Klassen?


2728

Wie teste ich eine Klasse (mit xUnit) mit internen privaten Methoden, Feldern oder verschachtelten Klassen? Oder eine Funktion, die durch interne Verknüpfung ( staticin 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.


257
Der beste Weg, um eine private Methode zu testen, ist nicht direkt zu testen
Surya


383
Ich stimme dir nicht zu. Eine (öffentliche) Methode, die langwierig oder schwer zu verstehen ist, muss überarbeitet werden. Es wäre töricht, nicht die kleinen (privaten) Methoden zu testen, die Sie erhalten, sondern nur die öffentliche.
Michael Piefel

181
Keine Methoden zu testen, nur weil die Sichtbarkeit dumm ist. Selbst bei Unit-Tests sollte es sich um den kleinsten Code handeln. Wenn Sie nur öffentliche Methoden testen, werden Sie nie sicher sein, wo ein Fehler auftritt - diese oder eine andere Methode.
Dainius

126
Sie müssen die Klasse testen Funktionalität , nicht seine Umsetzung . Möchten Sie die privaten Methoden testen? Testen Sie die öffentlichen Methoden, die sie aufrufen. Wenn die Funktionalität der Klasse gründlich getestet wird, haben sich die Interna der Klasse als korrekt und zuverlässig erwiesen. Sie müssen die internen Bedingungen nicht testen. Die Tests sollten die Entkopplung von den getesteten Klassen beibehalten.
Calimar41

Antworten:


1640

Aktualisieren:

Etwa 10 Jahre später ist der beste Weg, eine private Methode oder ein unzugängliches Mitglied zu testen, @Jailbreakdas Manifold- Framework.

@Jailbreak Foo foo = new Foo();
// Direct, *type-safe* access to *all* foo's members
foo.privateMethod(x, y, z);
foo.privateField = value;

Auf diese Weise bleibt Ihr Code typsicher und lesbar. Keine Designkompromisse, keine Überbelichtung von Methoden und Feldern für Tests.

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 privateund private staticVariablen sowie Aufrufe privateund private staticMethoden 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 Sie private static finalVariablen nicht durch Reflexion ändern .

Method method = TargetClass.getDeclaredMethod(methodName, argClasses);
method.setAccessible(true);
return method.invoke(targetObject, argObjects);

Und für Felder:

Field field = TargetClass.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(object, value);

Hinweise:
1. Hier TargetClass.getDeclaredMethod(methodName, argClasses)können Sie privateMethoden untersuchen. Gleiches gilt für getDeclaredField.
2. Das setAccessible(true)ist erforderlich, um mit Privaten herumzuspielen.


350
Nützlich, wenn Sie die API vielleicht nicht kennen, aber wenn Sie private Methoden auf diese Weise testen müssen, liegt etwas an Ihrem Design. Wie ein anderes Poster sagt, sollten Unit-Tests den Vertrag der Klasse testen: Wenn der Vertrag zu weit gefasst ist und zu viel vom System instanziiert, sollte das Design angesprochen werden.
Andygavin

21
Sehr hilfreich. Bei dieser Verwendung ist zu beachten, dass es schlimm fehlschlagen würde, wenn die Tests nach der Verschleierung durchgeführt würden.
Rick Minerich

20
Der Beispielcode hat bei mir nicht funktioniert, aber das hat die Details klarer gemacht: java2s.com/Tutorial/Java/0125__Reflection/…
Rob

35
Viel besser als die direkte Verwendung von Reflexion wäre es, eine Bibliothek wie Powermock dafür zu verwenden .
Michael Piefel

25
Aus diesem Grund ist Test Driven Design hilfreich. Es hilft Ihnen herauszufinden, was verfügbar gemacht werden muss, um das Verhalten zu validieren.
Thorbjørn Ravn Andersen

621

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:

  1. Die private Methode ist toter Code
  2. In der Nähe der Klasse, die Sie testen, riecht es nach Design
  3. Die Methode, die Sie testen möchten, sollte nicht privat sein

6
Darüber hinaus erreichen wir ohnehin nie eine 100% ige Codeabdeckung. Warum also nicht Ihre Zeit mit Qualitätstests auf die Methoden konzentrieren, die Kunden tatsächlich direkt verwenden?
grinch

30
@grinch Spot on. Das einzige, was Sie durch das Testen privater Methoden gewinnen würden, ist das Debuggen von Informationen, und dafür sind Debugger gedacht. Wenn Ihre Tests des Klassenvertrags vollständig abgedeckt sind, haben Sie alle Informationen, die Sie benötigen. Private Methoden sind ein Implementierungsdetail . Wenn Sie sie testen, müssen Sie Ihre Tests jedes Mal ändern, wenn sich Ihre Implementierung ändert, auch wenn der Vertrag dies nicht tut. Im gesamten Lebenszyklus der Software wird dies wahrscheinlich viel mehr kosten als der Nutzen, den sie bietet.
Erik Madsen

9
@AlexWien du hast recht. Berichterstattung war nicht der richtige Begriff. Was ich hätte sagen sollen, war "wenn Ihre Tests des Klassenvertrags alle bedeutungsvollen Eingaben und alle bedeutungsvollen Zustände abdecken". Sie weisen zu Recht darauf hin, dass dies möglicherweise nicht über die öffentliche Schnittstelle zum Code oder indirekt, wenn Sie darauf verweisen, getestet werden kann. Wenn dies bei einem Code der Fall ist, den Sie testen, würde ich entweder denken: (1) Der Vertrag ist zu großzügig (z. B. zu viele Parameter in der Methode) oder (2) Der Vertrag ist zu vage (z. B. Methodenverhalten) variiert stark mit dem Klassenstatus). Ich würde beide Designgerüche berücksichtigen.
Erik Madsen

16
Fast alle privaten Methoden sollten nicht direkt getestet werden (um die Wartungskosten zu senken). Ausnahme: Wissenschaftliche Methoden können Formen wie f (a (b (c (x), d (y))), a (e (x, z)), b (f (x, y, z), z) haben. wobei a, b, c, d, e und f schrecklich komplizierte Ausdrücke sind, aber ansonsten außerhalb dieser einzelnen Formel nutzlos sind. Einige Funktionen (denken Sie an MD5) sind schwer zu invertieren, so dass es schwierig (NP-schwer) sein kann, Parameter auszuwählen, die den Verhaltensraum vollständig abdecken. Es ist einfacher, a, b, c, d, e, f unabhängig voneinander zu testen. Sie öffentlich oder paketprivat zu machen, lügt, dass sie wiederverwendbar sind. Lösung: Machen Sie sie privat, aber testen Sie sie.
Namensgeber

4
@Eponymous Ich bin ein bisschen hin und her gerissen. Der objektorientierte Purist in mir würde sagen, dass Sie einen objektorientierten Ansatz anstelle statischer privater Methoden verwenden sollten, damit Sie über öffentliche Instanzmethoden testen können. Andererseits ist es in einem wissenschaftlichen Rahmen sehr wahrscheinlich, dass der Speicheraufwand ein Problem darstellt, das meines Erachtens für den statischen Ansatz spricht.
Erik Madsen

323

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.


24
Die Frage war, wie man private Methoden testet. Sie sagen, dass Sie dafür eine neue Klasse erstellen würden (und viel mehr Komplexität hinzufügen würden) und schlagen anschließend vor, keine neue Klasse zu erstellen. Wie also private Methoden testen?
Dainius

27
@Dainius: Ich behaupte nicht , Erstellen eine neue Klasse allein , so dass Sie diese Methode testen. Ich schlage vor, dass das Schreiben von Tests Ihnen helfen kann, Ihr Design zu verbessern: Gute Designs sind einfach zu testen.
Jay Bazuzi

13
Sie stimmen jedoch zu, dass eine gute OOD nur Methoden offenlegen (veröffentlichen) würde, die erforderlich sind, damit diese Klasse / dieses Objekt ordnungsgemäß funktioniert? Alle anderen sollten privat / schützend sein. Bei einigen privaten Methoden gibt es also eine gewisse Logik, und das Testen dieser Methoden durch IMO verbessert nur die Qualität der Software. Natürlich stimme ich zu, dass ein Teil des Codes, wenn er zu komplex ist, in separate Methoden / Klassen unterteilt werden sollte.
Dainius

6
@Danius: Das Hinzufügen einer neuen Klasse reduziert in den meisten Fällen die Komplexität. Messen Sie es einfach. Testbarkeit kämpft in einigen Fällen gegen OOD. Der moderne Ansatz begünstigt die Testbarkeit.
AlexWien

5
Das ist genau wie das Feedback von Ihren Tests Laufwerk zu verwenden und Ihr Design zu verbessern! Hören Sie sich Ihre Tests an. Wenn sie schwer zu schreiben sind, sagt Ihnen das etwas Wichtiges über Ihren Code!
pnschofield

289

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.


76
+1 dazu. Meiner Meinung nach ist es die beste Antwort auf die Frage. Durch Testen privater Methoden testen Sie die Implementierung. Dies macht den Zweck des Unit-Tests zunichte, nämlich das Testen der Ein- / Ausgänge eines Klassenvertrags. Ein Test sollte nur genug über die Implementierung wissen, um die Methoden zu verspotten, die er für seine Abhängigkeiten aufruft. Nichts mehr. Wenn Sie Ihre Implementierung nicht ändern können, ohne einen Test ändern zu müssen, ist Ihre Teststrategie wahrscheinlich schlecht.
Colin M

10
@Colin M Das ist aber nicht wirklich das, was er fragt;) Lass ihn es entscheiden, du kennst das Projekt nicht.
Aaron Marcus

2
Nicht wirklich wahr. Es kann sehr aufwändig sein, einen kleinen Teil der privaten Methode mithilfe der öffentlichen Methode zu testen. Die öffentliche Methode erfordert möglicherweise eine erhebliche Einrichtung, bevor Sie die Leitung erreichen, die Ihre private Methode aufruft
ACV

1
Genau. Sie sollten testen, ob der Vertrag erfüllt ist. Sie sollten nicht testen, wie dies gemacht wird. Wenn der Kontakt erfüllt wird, ohne eine 100% ige Codeabdeckung zu erreichen (bei privaten Methoden), kann dies toter oder nutzloser Code sein.
Wuppi

207

In diesem Artikel: Testen privater Methoden mit JUnit und SuiteRunner (Bill Venners) haben Sie grundsätzlich 4 Optionen:

  1. Testen Sie keine privaten Methoden.
  2. Geben Sie dem Methodenpaket Zugriff.
  3. Verwenden Sie eine verschachtelte Testklasse.
  4. Verwenden Sie Reflexion.

@JimmyT., Kommt darauf an, für wen der "Produktionscode" ist. Ich würde Code, der für in VPN stationierte Anwendungen erstellt wurde, deren Zielbenutzer Systemadministratoren sind, als Produktionscode bezeichnen.
Pacerier

@ Pacerier Was meinst du?
Jimmy T.

1
Die fünfte Option, wie oben erwähnt, ist das Testen der öffentlichen Methode, die die private Methode aufruft. Richtig?
user2441441

1
@LorenzoLerate Ich habe damit gerungen, weil ich Embedded-Entwicklung mache und ich möchte, dass meine Software so klein wie möglich ist. Größenbeschränkungen und Leistung sind der einzige Grund, an den ich denken kann.

Ich mag den Test mit Reflection sehr: Hier das Muster Method method = targetClass.getDeclaredMethod (methodName, argClasses); method.setAccessible (true); return method.invoke (targetObject, argObjects);
Aguid

127

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.


16
Das ist die beste Antwort IMO, oder wie es normalerweise gesagt wird, Testverhalten, nicht Methoden. Unit-Tests sind kein Ersatz für Quellcode-Metriken, statische Code-Analyse-Tools und Code-Überprüfungen. Wenn private Methoden so komplex sind, dass sie separate Tests benötigen, müssen sie wahrscheinlich überarbeitet werden, nicht mehr Tests.
Dan Haynes

14
Schreiben Sie also keine privaten Methoden, sondern erstellen Sie nur 10 andere kleine Klassen?
Rasiermesser

1
Nein, dies bedeutet im Grunde, dass Sie die Funktionalität der privaten Methoden mit der öffentlichen Methode testen können, die sie aufruft.
Akshat Sharda

67

Nur zwei Beispiele, wo ich eine private Methode testen möchte:

  1. Entschlüsselungsroutinen - Ich möchte sie nicht für jedermann sichtbar machen, um sie nur zum Testen zu sehen, sonst kann jeder sie zum Entschlüsseln verwenden. Sie sind jedoch dem Code eigen, kompliziert und müssen immer funktionieren (die offensichtliche Ausnahme ist die Reflexion, mit der in den meisten Fällen sogar private Methoden angezeigt werden können, wenn dies SecurityManagernicht konfiguriert ist, um dies zu verhindern).
  2. Erstellen eines SDK für den Community-Verbrauch. Hier hat die Öffentlichkeit eine ganz andere Bedeutung, da dies Code ist, den die ganze Welt sehen kann (nicht nur innerhalb meiner Anwendung). Ich setze Code in private Methoden ein, wenn ich nicht möchte, dass die SDK-Benutzer ihn sehen. Ich sehe dies nicht als Codegeruch, sondern nur als Funktionsweise der SDK-Programmierung. Aber natürlich muss ich meine privaten Methoden noch testen, und dort lebt die Funktionalität meines SDK tatsächlich.

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.


5
Während Sie niemals eine Methode veröffentlichen sollten, nur um sie zu testen, privateist dies nicht die einzige Alternative. Kein Zugriffsmodifikator ist package privateund bedeutet, dass Sie ihn einem Unit-Test unterziehen können, solange sich Ihr Unit-Test im selben Paket befindet.
Ngreen

1
Ich habe Ihre Antwort kommentiert, insbesondere Punkt 1, nicht das OP. Es ist nicht erforderlich, eine Methode privat zu machen, nur weil Sie nicht möchten, dass sie öffentlich ist.
Grün

1
@ngreen true thx - Ich war faul mit dem Wort "public". Ich habe die Antwort so aktualisiert, dass sie öffentlich, geschützt, standardmäßig (und zur Erwähnung der Reflexion) enthält. Der Punkt, den ich ansprechen wollte, ist, dass es einen guten Grund gibt, dass ein Code geheim ist, aber das sollte uns nicht daran hindern, ihn zu testen.
Richard Le Mesurier

2
Vielen Dank, dass Sie allen echte Beispiele geben. Die meisten Antworten sind gut, aber theoretisch. In der realen Welt müssen wir die Implementierung vor dem Vertrag verbergen und sie noch testen. Wenn ich all dies
lese

1
Ein weiteres Beispiel - Crawler-HTML-Parser . Um eine ganze HTML-Seite in Domänenobjekte zu analysieren, ist eine Menge Logik erforderlich, um kleine Teile der Struktur zu validieren. Es ist sinnvoll, dies in Methoden aufzuteilen und von einer öffentlichen Methode aufzurufen. Es ist jedoch nicht sinnvoll, alle kleineren Methoden, aus denen es besteht, öffentlich zu machen, und es ist auch nicht sinnvoll, 10 Klassen pro HTML-Seite zu erstellen.
Nether

59

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.


Wahre Tatsache. Das Problem ist, wenn die Implementierung komplizierter ist, beispielsweise wenn eine öffentliche Methode mehrere private Methoden aufruft. Welches ist gescheitert? Oder wenn es sich um eine komplizierte private Methode handelt, die weder entlarvt noch einer neuen Klasse übertragen werden kann
Z. Khullah,

Sie haben bereits erwähnt, warum die private Methode getestet werden sollte. Sie sagten "dann könnte das sein", also sind Sie sich nicht sicher. IMO, ob eine Methode getestet werden soll, ist orthogonal zu ihrer Zugriffsebene.
Wei Qiu

39

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.javaIhr Testaufruf in platziert werden src/test/java/mypackage/MyClassTest.java. Auf diese Weise haben Sie Zugriff auf die Testmethode in Ihrer Testklasse.


1
Ich wusste nichts über dieses, es ist interessant, ich denke immer noch, dass Sie Designprobleme haben, wenn Sie diese Art von Annottaion brauchen.
Seb

2
Für mich ist das so, als würde man sagen, anstatt Ihrem Feuerwehrmann einen einmaligen Schlüssel zum Testen der Feuerwehrübung zu geben, lassen Sie Ihre Haustür unverschlossen mit einem Schild an der Tür, auf dem steht: "Zum Testen entsperrt - wenn Sie nicht von sind." unsere Firma, bitte nicht eingeben ".
Pater Jeremy Krieg

@FrJeremyKrieg - was heißt das?
MasterJoe2

1
@ MasterJoe2: Der Zweck eines Brandtests besteht darin, die Sicherheit Ihres Gebäudes gegen die Brandgefahr zu verbessern. Aber Sie müssen dem Aufseher Zugang zu Ihrem Gebäude gewähren. Wenn Sie die Vordertür dauerhaft unverschlossen lassen, erhöhen Sie das Risiko eines unbefugten Zugriffs und machen sie weniger sicher. Gleiches gilt für das VisibleForTesting-Muster: Sie erhöhen das Risiko eines nicht autorisierten Zugriffs, um das Risiko eines "Feuers" zu verringern. Es ist besser, den Zugriff als einmaligen Test nur dann zu gewähren, wenn dies erforderlich ist (z. B. mithilfe von Reflection), als ihn dauerhaft entsperrt zu lassen (nicht privat).
Pater Jeremy Krieg

34

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.

import junitx.util.PrivateAccessor;

PrivateAccessor.setField(myObjectReference, "myCrucialButHardToReachPrivateField", myNewValue);
PrivateAccessor.invoke(myObjectReference, "privateMethodName", java.lang.Class[] parameterTypes, java.lang.Object[] args);

2
Stellen Sie sicher, dass Sie das gesamte JUnit-Addons- Paket ( sourceforge.net/projects/junit-addons ) herunterladen , nicht das von Source Forge empfohlene Projekt PrivateAccessor.
skia.heliou

2
Und stellen Sie sicher Class, dass Sie nur auf staticMitglieder zugreifen , wenn Sie in diesen Methoden einen als ersten Parameter verwenden .
skia.heliou

31

Im Spring Framework können Sie private Methoden mit folgender Methode testen:

ReflectionTestUtils.invokeMethod()

Zum Beispiel:

ReflectionTestUtils.invokeMethod(TestClazz, "createTest", "input data");

Die bisher sauberste und prägnanteste Lösung, aber nur, wenn Sie Spring verwenden.
Denis Nikolaenko

28

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:

  • Kann auf eine feinere Granularität getestet werden

Nachteile:

  • Der Testcode muss sich in derselben Datei wie der Quellcode befinden, was möglicherweise schwieriger zu warten ist
  • Ähnlich wie bei Ausgabedateien der Klasse müssen sie im selben Paket verbleiben, wie im Quellcode deklariert

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:

// Import statements and package declarations

public class ClassToTest
{
    private int decrement(int toDecrement) {
        toDecrement--;
        return toDecrement;
    }

    // Constructor and the rest of the class

    public static class StaticInnerTest extends TestCase
    {
        public StaticInnerTest(){
            super();
        }

        public void testDecrement(){
            int number = 10;
            ClassToTest toTest= new ClassToTest();
            int decremented = toTest.decrement(number);
            assertEquals(9, decremented);
        }

        public static void main(String[] args) {
            junit.textui.TestRunner.run(StaticInnerTest.class);
        }
    }
}

Die innere Klasse würde zusammengestellt werden ClassToTest$StaticInnerTest.

Siehe auch: Java-Tipp 106: Statische innere Klassen für Spaß und Gewinn


26

Wie andere gesagt haben ... testen Sie private Methoden nicht direkt. Hier einige Gedanken:

  1. Halten Sie alle Methoden klein und konzentriert (leicht zu testen, leicht zu finden, was falsch ist)
  2. Verwenden Sie Tools zur Codeabdeckung. Ich mag Cobertura (oh glücklicher Tag, sieht aus wie eine neue Version heraus ist!)

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.


Bis zur Codeabdeckung. Unabhängig davon, welche Art von Logik in der privaten Methode enthalten ist, rufen Sie diese Logik immer noch über eine öffentliche Methode auf. Ein Tool zur Codeabdeckung kann Ihnen zeigen, welche Teile vom Test abgedeckt werden. Daher können Sie sehen, ob Ihre private Methode getestet wurde.
Coolcfan

Ich habe Klassen gesehen, in denen die einzige öffentliche Methode main [] ist, und sie öffnen die GUI und verbinden die Datenbank und einige Webserver weltweit. Einfach zu sagen "nicht indirekt testen" ...
Audrius Meskauskas

26

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.


2
Obwohl dies zutrifft, kann es zu einigen sehr komplizierten Tests kommen - es ist ein besseres Muster, eine einzelne Methode gleichzeitig zu testen (Unit-Test), als eine ganze Gruppe von ihnen. Kein schrecklicher Vorschlag, aber ich denke, hier gibt es bessere Antworten - dies sollte ein letzter Ausweg sein.
Bill K

24

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:

ReflectionTestUtils.setField(theClass, "theUnsettableField", theMockObject);

24

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.


19

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.


4
Unit Test und SRC-Code sind ein Paar. Wenn Sie den Quellcode ändern, müssen Sie möglicherweise den Komponententest ändern. Das ist der Sinn des Junit-Tests. Sie sollen garantieren, dass alles wie zuvor funktioniert. und es ist in Ordnung, wenn sie brechen, wenn Sie den Code ändern.
AlexWien

1
Stimmen Sie nicht zu, dass sich der Komponententest ändern sollte, wenn sich der Code ändert. Wenn Sie angewiesen werden, einer Klasse Funktionen hinzuzufügen, ohne die vorhandene Funktionalität der Klasse zu ändern, sollten die vorhandenen Komponententests auch nach dem Ändern Ihres Codes bestanden werden. Wie Peter erwähnt, sollten Unit-Tests die Schnittstelle testen, nicht wie sie intern durchgeführt wird. In Test Driven Development werden Unit-Tests erstellt, bevor der Code geschrieben wird, um sich auf die Schnittstelle der Klasse zu konzentrieren, nicht darauf, wie sie intern gelöst wird.
Harald Coppoolse

16

Die Antwort von der FAQ-Seite von JUnit.org :

Aber wenn Sie müssen ...

Wenn Sie JDK 1.3 oder höher verwenden, können Sie mithilfe von Reflection den Zugriffssteuerungsmechanismus mithilfe des PrivilegedAccessor untergraben . Weitere Informationen zur Verwendung finden Sie in diesem Artikel .

Wenn Sie JDK 1.6 oder höher verwenden und Ihre Tests mit @Test kommentieren, können Sie Dp4j verwenden , um Reflexion in Ihre Testmethoden einzufügen . Einzelheiten zur Verwendung finden Sie in diesem Testskript .

PS Ich bin der Hauptverantwortliche für Dp4j . Frag mich, ob du Hilfe brauchst. :) :)


16

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.


4
Als Teil der jmockit-Bibliothek haben Sie Zugriff auf die Deencapsulation-Klasse, die das Testen privater Methoden erleichtert: Deencapsulation.invoke(instance, "privateMethod", param1, param2);
Domenic D.

Ich benutze diesen Ansatz die ganze Zeit. Sehr hilfreich
MedicineMan

14

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).


14

Wenn Sie JUnit verwenden, schauen Sie sich Junit-Addons an . Es kann das Java-Sicherheitsmodell ignorieren und auf private Methoden und Attribute zugreifen.


13

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:

public class ClassToTest {

    private final String first = "first";
    private final List<String> second = new ArrayList<>();
    ...
}

Ich hätte das benutzt:

public class ClassToTest {

    private final String first;
    private final List<String> second;

    public ClassToTest() {
        this("first", new ArrayList<>());
    }

    public ClassToTest(final String first, final List<String> second) {
        this.first = first;
        this.second = second;
    }
    ...
}

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 , Callableund dann übergeben Sie diese Schnittstelle auch im Konstruktor (mit diesem Trick mit mehreren Konstruktoren):

public ClassToTest() {
    this(...);
}

public ClassToTest(final Callable<T> privateMethodLogic) {
    this.privateMethodLogic = privateMethodLogic;
}

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.


1
Nicht einverstanden mit der ClassToTest(Callable)Injektion. Das macht ClassToTestes komplizierter. Halte es einfach. Dazu muss dann jemand anderesClassToTest etwas sagen , ClassToTestdessen 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.
Loduwijk

Es ist auch vorzuziehen, wenn Ihre Testmethode Xdie XKomplexitä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 die
vorhergehende

2
Können Sie erklären, warum es ClassToTestschwieriger 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 in firstund für 'zweite' Variablen benötigen ?
GROX13

1
Die Testmethode erhöht nicht die Komplexität. Es ist nur deine Klasse, die schlecht geschrieben ist, so schlecht, dass sie nicht getestet werden kann.
GROX13

Schwieriger zu pflegen, da die Klasse komplizierter ist. class A{ A(){} f(){} }ist einfacher als class 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önnen class 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"
Loduwijk

12

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.


Sie mischen "geschützt" mit "freundlich". Auf eine geschützte Methode kann nur von einer Klasse zugegriffen werden, deren Objekte der Zielklasse (dh Unterklassen) zugewiesen werden können.
Sayo Oladeji

1
Tatsächlich gibt es in Java kein "Friendly", der Begriff ist "Package" und wird durch das Fehlen eines privaten / öffentlichen / geschützten Modifikators angezeigt. Ich hätte diese Antwort gerade korrigiert, aber es gibt eine wirklich gute, die dies bereits sagt - daher würde ich nur empfehlen, sie zu löschen.
Bill K

Ich habe fast abgelehnt und dachte die ganze Zeit: "Diese Antwort ist einfach falsch." bis ich zum allerletzten Satz kam, der dem Rest der Antwort widerspricht. Der letzte Satz hätte der erste sein sollen.
Loduwijk

11

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.


Sie sollten nicht indirekt testen! Nicht nur durch Berichterstattung berühren; Testen Sie, ob das erwartete Ergebnis geliefert wird!
AlexWien

11

Hier ist meine generische Funktion zum Testen privater Felder:

protected <F> F getPrivateField(String fieldName, Object obj)
    throws NoSuchFieldException, IllegalAccessException {
    Field field =
        obj.getClass().getDeclaredField(fieldName);

    field.setAccessible(true);
    return (F)field.get(obj);
}

10

Ein Beispiel finden Sie weiter unten.

Die folgende Importanweisung sollte hinzugefügt werden:

import org.powermock.reflect.Whitebox;

Jetzt können Sie das Objekt mit der privaten Methode, dem aufzurufenden Methodennamen und den folgenden zusätzlichen Parametern direkt übergeben.

Whitebox.invokeMethod(obj, "privateMethod", "param1");

10

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:

@UiThreadTest
public void testCompute() {

    // Given
    boundBoxOfMainActivity = new BoundBoxOfMainActivity(getActivity());

    // When
    boundBoxOfMainActivity.boundBox_getButtonMain().performClick();

    // Then
    assertEquals("42", boundBoxOfMainActivity.boundBox_getTextViewMain().getText());
}

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


1
Gerade ausprobiert, BoundBox ist eine einfache und elegante Lösung!
Forhas

9

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.


3
Denn oft wird ein bestimmter Code aus einer öffentlichen Methode in eine interne private Methode umgestaltet und ist wirklich die kritische Logik, die Sie möglicherweise falsch verstanden haben. Sie möchten dies unabhängig von der öffentlichen Methode
testen

Selbst der kürzeste Code ohne Unit-Test ist manchmal nicht korrekt. Versuchen Sie einfach, den Unterschied zwischen zwei geografischen Winkeln zu berechnen. 4 Codezeilen, und die meisten werden es beim ersten Versuch nicht richtig machen. Solche Methoden erfordern einen Komponententest, da sie die Basis eines vertrauenswürdigen Codes bilden. (Ans solcher nützlicher Code kann auch öffentlich sein; weniger nützlich geschützt
AlexWien

8

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-Reflektion

Method method = targetClass.getDeclaredMethod("method", String.class);
method.setAccessible(true);
return method.invoke(targetObject, "mystring");

private void method(String s)Aufrufmethoden , zB - von Picklock

interface Accessible {
  void method(String s);
}

...
Accessible a = ObjectAccess.unlock(targetObject).features(Accessible.class);
a.method("mystring");

Einstellen von Feldern, zB private BigInteger amount;- durch Java-Reflexion

Field field = targetClass.getDeclaredField("amount");
field.setAccessible(true);
field.set(object, BigInteger.valueOf(42));

Einstellen von Feldern, zB private BigInteger amount;- durch Picklock

interface Accessible {
  void setAmount(BigInteger amount);
}

...
Accessible a = ObjectAccess.unlock(targetObject).features(Accessible.class);
a.setAmount(BigInteger.valueOf(42));

7

PowerMockito ist dafür gemacht. Verwenden Sie die Maven-Abhängigkeit

<dependency>
    <groupId>org.powermock</groupId>
    <artifactId>powermock-mockito-release-full</artifactId>
    <version>1.6.4</version>
</dependency>

Dann können Sie tun

import org.powermock.reflect.Whitebox;
...
MyClass sut = new MyClass();
SomeType rval = Whitebox.invokeMethod(sut, "myPrivateMethod", params, moreParams);
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.