Eine private Methode öffentlich machen, um sie zu testen… gute Idee?


301

Moderator Hinweis: Hier wurden bereits 39 Antworten veröffentlicht (einige wurden gelöscht). Überlegen Sie sich vor dem Veröffentlichen Ihrer Antwort, ob Sie der Diskussion etwas Sinnvolles hinzufügen können. Sie wiederholen höchstwahrscheinlich nur das, was bereits jemand anderes gesagt hat.


Gelegentlich muss ich eine private Methode in einer Klasse öffentlich machen, um einige Unit-Tests dafür zu schreiben.

Normalerweise liegt dies daran, dass die Methode Logik enthält, die von anderen Methoden in der Klasse gemeinsam genutzt wird, und dass es einfacher ist, die Logik selbst zu testen, oder dass ein anderer Grund möglich sein könnte, wenn ich die in synchronen Threads verwendete Logik testen möchte, ohne mir Gedanken über Threading-Probleme machen zu müssen .

Tun dies andere Leute, weil ich es nicht wirklich mag? Ich persönlich denke, dass die Boni die Probleme überwiegen, eine Methode öffentlich zu machen, die außerhalb der Klasse keinen wirklichen Service bietet ...

AKTUALISIEREN

Vielen Dank für die Antworten an alle, scheint das Interesse der Menschen geweckt zu haben. Ich denke, der allgemeine Konsens ist, dass das Testen über die öffentliche API erfolgen sollte, da dies die einzige Möglichkeit ist, eine Klasse jemals zu verwenden, und ich stimme dem zu. Die paar Fälle, die ich oben erwähnt habe, in denen ich dies oben tun würde, waren ungewöhnliche Fälle, und ich dachte, die Vorteile wären es wert.

Ich kann jedoch sehen, dass jeder darauf hinweist, dass es niemals wirklich passieren sollte. Und wenn ich ein bisschen mehr darüber nachdenke, denke ich, dass es eine schlechte Idee ist, Ihren Code zu ändern, um Tests zu ermöglichen - schließlich ist das Testen in gewisser Weise ein Support-Tool, und das Ändern eines Systems, um ein Support-Tool zu unterstützen, wenn Sie so wollen, ist offensichtlich schlechte Praxis.


2
"Ein System so zu ändern, dass es ein Support-Tool unterstützt, wenn Sie so wollen, ist eine offensichtliche schlechte Praxis." Na ja, irgendwie. Aber OTOH , wenn Ihr System nicht gut mit etablierten Tool funktioniert, vielleicht etwas ist falsch mit dem System.
Thilo


1
Keine Antwort, aber Sie können jederzeit durch Reflexion darauf zugreifen.
Zano

33
Nein, du solltest sie nicht aussetzen. Stattdessen sollten Sie über die öffentliche API testen, ob sich Ihre Klasse so verhält, wie sie sollte. Und wenn das Offenlegen von Interna wirklich die einzige Option ist (was ich bezweifle), sollten Sie zumindest das Accessors-Paket schützen, damit nur Klassen im selben Paket (wie Ihr Test sein sollte) darauf zugreifen können.
JB Nizet

4
Ja, so ist es. Es ist durchaus akzeptabel, einer Methode eine paketprivate (Standard-) Sichtbarkeit zu geben, um sie für Komponententests zugänglich zu machen. Bibliotheken wie Guava bieten sogar eine @VisibileForTestingAnmerkung, um solche Methoden zu erstellen. Ich empfehle Ihnen, dies zu tun, damit der Grund dafür, dass die Methode nicht privateordnungsgemäß dokumentiert ist.
Boris die Spinne

Antworten:


186

Hinweis:
Diese Antwort wurde ursprünglich für die Frage veröffentlicht. Ist Unit-Test allein jemals ein guter Grund, private Instanzvariablen über Getter verfügbar zu machen? das in dieses verschmolzen wurde, so dass es ein bisschen spezifisch für den dort vorgestellten Anwendungsfall sein kann.

Generell bin ich normalerweise alle dafür, "Produktions" -Code umzugestalten, um das Testen zu vereinfachen. Ich denke jedoch nicht, dass dies hier ein guter Anruf wäre. Ein guter Komponententest sollte sich (normalerweise) nicht um die Implementierungsdetails der Klasse kümmern, sondern nur um ihr sichtbares Verhalten. Anstatt die internen Stapel dem Test auszusetzen, können Sie testen, ob die Klasse die Seiten in der Reihenfolge zurückgibt, in der Sie sie nach dem Aufruf von first()oder erwarten last().

Betrachten Sie zum Beispiel diesen Pseudocode:

public class NavigationTest {
    private Navigation nav;

    @Before
    public void setUp() {
        // Set up nav so the order is page1->page2->page3 and
        // we've moved back to page2
        nav = ...;
    }

    @Test
    public void testFirst() {
        nav.first();

        assertEquals("page1", nav.getPage());

        nav.next();
        assertEquals("page2", nav.getPage());

        nav.next();
        assertEquals("page3", nav.getPage());
    }

    @Test
    public void testLast() {
        nav.last();

        assertEquals("page3", nav.getPage());

        nav.previous();
        assertEquals("page2", nav.getPage());

        nav.previous();
        assertEquals("page1", nav.getPage());
    }
}

8
Ich stimme Ihrer Antwort voll und ganz zu. Viele Entwickler wären versucht, den Standardzugriffsmodifikator zu verwenden und zu rechtfertigen, dass gute Open Source-Frameworks diese Strategie verwenden, sodass sie korrekt sein muss. Nur weil du kannst, heißt das nicht, dass du es solltest. +1
CKing

6
Obwohl andere nützliche Erkenntnisse geliefert haben, muss ich sagen, dass dies die Lösung ist, die ich für dieses Problem am besten geeignet finde. Es hält die Tests völlig unabhängig davon, wie das beabsichtigte Verhalten intern erreicht wird. +10!
Gitahi Ng'ang'a

Ist es übrigens in Ordnung, dass in dem hier von @Mureinik bereitgestellten Beispiel die Testmethoden mehr abdecken als die tatsächlich getestete Methode? Zum Beispiel ruft testFirst () next () auf, was hier nicht wirklich die zu testende Methode ist, first () ist. Wäre es nicht sauberer und klarer, nur eine Testmethode zu haben, die alle 4 Navigationsmethoden abdeckt?
Gitahi Ng'ang'a

1
Obwohl dies das Ideal ist, müssen Sie manchmal überprüfen, ob eine Komponente etwas richtig gemacht hat, nicht nur, dass es erfolgreich war. Dies kann für den Black-Box-Test schwieriger sein. Manchmal müssen Sie eine Komponente im White-Box-Test testen.
Peter Lawrey

1
Getter nur zum Testen von Einheiten erstellen? Dies widerspricht definitiv dem Korn von OOP / Object Thinking , da die Schnittstelle Verhalten und nicht Daten offenlegen sollte.
IntelliData

151

Persönlich würde ich lieber einen Unit-Test mit der öffentlichen API durchführen und die private Methode sicherlich nie öffentlich machen, nur um das Testen zu vereinfachen.

Wenn Sie die private Methode wirklich isoliert testen möchten, können Sie in Java Easymock / Powermock verwenden , um dies zu ermöglichen.

Sie müssen pragmatisch sein und sich auch der Gründe bewusst sein, warum es schwierig ist, Dinge zu testen.

" Hören Sie sich die Tests an " - wenn es schwierig zu testen ist, sagt Ihnen das etwas über Ihr Design aus? Könnten Sie umgestalten, wo ein Test für diese Methode trivial wäre und leicht durch Tests durch die öffentliche API abgedeckt werden könnte?

Hier ist, was Michael Feathers in " Effektiv mit Legacy-Code arbeiten " zu sagen hat.

"Viele Leute verbringen viel Zeit damit, herauszufinden, wie sie dieses Problem umgehen können. Die eigentliche Antwort lautet: Wenn Sie den Drang haben, eine private Methode zu testen, sollte die Methode nicht privat sein. Wenn Sie die Methode öffentlich machen stört Sie wahrscheinlich, weil es Teil einer separaten Verantwortung ist; es sollte in einer anderen Klasse sein. " [ Effektiv mit Legacy Code arbeiten (2005) von M. Feathers]


5
+1 für Easymock / Powermock und niemals eine private Methode öffentlich machen
Leonard Brünings

51
+1 Für "Hören Sie sich die Tests an". Wenn Sie eine private Methode testen möchten, stehen die Chancen gut, dass die Logik in dieser Methode so komplex ist, dass sie sich in einer separaten Hilfsklasse befinden sollte. Dann können Sie die Hilfsklasse testen.
Phil

2
"Hören Sie sich die Tests an" scheint nicht verfügbar zu sein. Hier ist ein zwischengespeicherter Link: webcache.googleusercontent.com/…
Jess Telford

1
Ich stimme @Phil zu (insbesondere der Buchreferenz), befinde mich jedoch häufig in einer Henne / Ei-Situation mit altem Code. Ich weiß, dass diese Methode in eine andere Klasse gehört, aber ich bin nicht 100% zufrieden mit Refactoring ohne Tests.
Kevin

Ich stimme dem Arzt zu. Wenn Sie eine private Methode testen müssen, sollte sie sich wahrscheinlich in einer anderen Klasse befinden. Möglicherweise stellen Sie fest, dass die andere Klasse / Hilfsklasse dieser privaten Methode wahrscheinlich öffentlich / geschützt wird.
Rupweb

67

Wie andere gesagt haben, ist es etwas verdächtig, überhaupt private Methoden zu testen; Unit-Test der öffentlichen Schnittstelle, nicht der privaten Implementierung Details.

Die Technik, die ich verwende, wenn ich in C # etwas Privates testen möchte, besteht darin, den Zugriffsschutz von privat auf intern herunterzustufen und dann die Unit-Test-Assembly mit InternalsVisibleTo als Friend-Assembly zu markieren . Die Einheitentestbaugruppe kann dann die Einbauten als öffentlich behandeln, Sie müssen sich jedoch keine Sorgen machen, dass Ihre öffentliche Oberfläche versehentlich vergrößert wird.


Ich benutze diese Technik auch, aber normalerweise als allerletzter Ausweg für Legacy-Code. Wenn Sie privaten Code testen müssen, fehlt eine Klasse / die zu testende Klasse tut zu viel. Extrahieren Sie den Code in eine neue Klasse, die Sie isoliert testen können. Tricks wie diese sollten selten verwendet werden.
Finglas

Wenn Sie jedoch eine Klasse extrahieren und nur testen, verlieren Ihre Tests das Wissen, dass die ursprüngliche Klasse den extrahierten Code tatsächlich verwendet . Wie empfehlen Sie, diese Lücke in Ihren Tests zu schließen? Und wenn die Antwort darin besteht, die öffentliche Schnittstelle der ursprünglichen Klasse nur so zu testen, dass die extrahierte Logik verwendet wird, warum sollte man sie dann überhaupt extrahieren? (Ich will hier übrigens nicht konfrontativ klingen - ich habe mich nur immer gefragt, wie die Leute solche Kompromisse ausgleichen.)
Mal Ross

@mal Ich hätte einen Kollaborationstest. Mit anderen Worten, ein Mock zur Überprüfung der neuen Klasse wurde korrekt aufgerufen. Siehe meine Antwort unten.
Finglas

Könnte man eine Testklasse schreiben, die von der Klasse mit interner Logik erbt, die Sie testen möchten, und eine öffentliche Methode zum Testen der internen Logik bereitstellen?
Sholsinger

1
@sholsinger: In C # erbt eine öffentliche Klasse möglicherweise nicht von einer internen Klasse.
Eric Lippert

45

Viele Antworten schlagen vor, nur die öffentliche Schnittstelle zu testen, aber meiner Meinung nach ist dies unrealistisch. Wenn eine Methode 5 Schritte ausführt, sollten Sie diese fünf Schritte separat und nicht alle zusammen testen. Dies erfordert das Testen aller fünf Methoden, die (außer zum Testen) ansonsten möglich wären private.

Die übliche Methode zum Testen von "privaten" Methoden besteht darin, jeder Klasse eine eigene Schnittstelle zuzuweisen und die "privaten" Methoden zu publicerstellen, sie jedoch nicht in die Schnittstelle aufzunehmen. Auf diese Weise können sie zwar noch getestet werden, aber die Benutzeroberfläche wird nicht aufgebläht.

Ja, dies führt zu einem Aufblähen von Dateien und Klassen.

Ja, dies macht die publicund privateBezeichner überflüssig.

Ja, das ist ein Schmerz im Arsch.

Dies ist leider eines der vielen Opfer, die wir bringen, um Code testbar zu machen . Möglicherweise verfügt eine zukünftige Sprache (oder sogar eine zukünftige Version von C # / Java) über Funktionen, die das Testen von Klassen und Modulen komfortabler machen. aber in der Zwischenzeit müssen wir durch diese Reifen springen.


Es gibt einige, die argumentieren würden, dass jeder dieser Schritte eine eigene Klasse sein sollte , aber ich bin anderer Meinung - wenn sie alle den Status teilen, gibt es keinen Grund, fünf separate Klassen zu erstellen, in denen fünf Methoden funktionieren würden. Schlimmer noch, dies führt zu einem Aufblähen von Dateien und Klassen. Außerdem infiziert es die öffentliche API Ihres Moduls - all diese Klassen müssen es unbedingt sein, publicwenn Sie sie von einem anderen Modul aus testen möchten (entweder das oder den Testcode in dasselbe Modul aufnehmen, was bedeutet, dass der Testcode mit Ihrem Produkt geliefert wird). .


2
schöne Antwort für 1. Hälfte
cmcginty

7
+1: Die beste Antwort für mich. Wenn ein Autohersteller aus dem gleichen Grund ein Teil seines Autos nicht testet, bin ich mir nicht sicher, ob es jemand kaufen würde. Ein wichtiger Teil des Codes sollte getestet werden, auch wenn wir ihn in öffentlich ändern sollten.
Tae-Sung Shin

1
Sehr gute Antwort. Du hast meine herzliche Stimme. Ich habe eine Antwort hinzugefügt. Es könnte dich interessieren.
Davidxxx

29

Ein Unit-Test sollte den öffentlichen Auftrag testen, die einzige Möglichkeit, wie eine Klasse in anderen Teilen des Codes verwendet werden kann. Eine private Methode besteht aus Implementierungsdetails. Sie sollten sie nicht testen. Soweit die öffentliche API ordnungsgemäß funktioniert, spielt die Implementierung keine Rolle und kann ohne Änderungen in Testfällen geändert werden.



Afair, Die seltene Situation, in der ich mich jemals für die Verwendung von Privates in einem Testfall entschieden habe, war die Überprüfung der Objektkorrektheit - wenn alle JNI-Handler geschlossen wurden. Offensichtlich wird es nicht als öffentliche API verfügbar gemacht. Wenn dies jedoch nicht der Fall ist, tritt ein Speicherverlust auf. Aber lustig, später habe ich es auch losgeworden, nachdem ich einen Speicherleckdetektor als Teil der öffentlichen API eingeführt habe.
Kan

11
Dies ist eine fehlerhafte Logik. Der Punkt des Unit-Tests besteht darin, Fehler früher als später zu erkennen. Wenn Sie keine privaten Methoden testen, hinterlassen Sie Lücken in Ihren Tests, die möglicherweise erst viel später entdeckt werden. In dem Fall, in dem die private Methode komplex ist und von einer öffentlichen Schnittstelle nicht leicht ausgeübt werden kann, sollte sie einem Unit-Test unterzogen werden.
cmcginty

6
@Casey: Wenn die private Methode nicht von einer öffentlichen Schnittstelle ausgeübt wird, warum ist sie dort?
Thilo

2
@DaSilva_Ireland: Es wird in Zukunft schwierig sein, Testfälle zu pflegen. Der einzige reale Klassenanwendungsfall ist die öffentliche Methode. Das heißt, alle anderen Codes können die Klasse nur über die öffentliche API verwenden. Wenn Sie also die Implementierung ändern möchten und der Testfall für private Methoden fehlschlägt, bedeutet dies nicht, dass Sie etwas falsch machen. Wenn Sie nur private, aber nicht vollständig öffentliche Tests durchführen, werden einige potenzielle Fehler nicht abgedeckt . Idealerweise sollte ein Testfall alle möglichen Fälle und nur Fälle der tatsächlichen Verwendung der Klasse abdecken.
Kan

22

IMO, Sie sollten Ihre Tests schreiben und keine tiefen Annahmen darüber treffen, wie Ihre Klasse im Inneren implementiert wurde. Sie möchten es wahrscheinlich später mit einem anderen internen Modell umgestalten, aber dennoch die gleichen Garantien geben, die die vorherige Implementierung bietet.

Vor diesem Hintergrund empfehle ich Ihnen, sich darauf zu konzentrieren, zu testen, ob Ihr Vertrag noch gültig ist, unabhängig davon, welche interne Implementierung Ihre Klasse derzeit hat. Eigenschaftsbasiertes Testen Ihrer öffentlichen APIs.


6
Ich bin damit einverstanden, dass Unit-Tests, die nach einem Refactoring des Codes nicht neu geschrieben werden müssen, besser sind. Und das nicht nur wegen der Zeit, die Sie für das Umschreiben der Komponententests aufwenden würden. Ein viel wichtigerer Grund ist, dass ich den wichtigsten Zweck von Unit-Tests darin sehe, dass sich Ihr Code nach einem Refactoring immer noch korrekt verhält. Wenn Sie nach einem Refactoring alle Unit-Tests neu schreiben müssen, verlieren Sie diesen Vorteil der Unit-Tests.
Kasperd

20

Wie wäre es mit einem privaten Paket? Dann kann Ihr Testcode es sehen (und andere Klassen in Ihrem Paket auch), aber es ist immer noch vor Ihren Benutzern verborgen.

Aber wirklich, Sie sollten keine privaten Methoden testen. Dies sind Implementierungsdetails und nicht Bestandteil des Vertrags. Alles, was sie tun, sollte durch Aufrufen der öffentlichen Methoden abgedeckt werden (wenn sie Code enthalten, der nicht von den öffentlichen Methoden ausgeübt wird, sollte dies geschehen). Wenn der private Code zu komplex ist, macht die Klasse wahrscheinlich zu viele Dinge und möchte nicht umgestaltet werden.

Eine Methode öffentlich zu machen ist eine große Verpflichtung. Sobald Sie dies tun, können die Benutzer es verwenden, und Sie können sie nicht mehr einfach ändern.


+1 Wenn Sie Ihre Privaten testen müssen, fehlt Ihnen wahrscheinlich eine Klasse
jk.

+1 für fehlende Klasse. Ich bin froh zu sehen, dass andere nicht sofort auf den Zug "Mark it Internal" springen.
Finglas

Junge, ich musste einen langen Weg nach unten gehen, um die private Antwort des Pakets zu finden.
Bill K

17

Aktualisieren : Ich habe diese Frage an zahlreichen anderen Stellen ausführlicher und vollständiger beantwortet. Dies ist auf meinem Blog zu finden .

Wenn ich jemals etwas öffentlich machen muss, um es zu testen, deutet dies normalerweise darauf hin, dass das zu testende System nicht dem folgt testende Prinzip Einzelverantwortung folgt . Daher fehlt eine Klasse, die eingeführt werden sollte. Nachdem Sie den Code in eine neue Klasse extrahiert haben, machen Sie ihn öffentlich. Jetzt können Sie problemlos testen und folgen SRP. Ihre andere Klasse muss diese neue Klasse einfach über Komposition aufrufen.

Das Veröffentlichen von Methoden / das Verwenden von Langauge-Tricks wie das Markieren von Code als sichtbar zum Testen von Baugruppen sollte immer das letzte Mittel sein.

Zum Beispiel:

public class SystemUnderTest
{
   public void DoStuff()
   {
      // Blah
      // Call Validate()
   }

   private void Validate()
   {
      // Several lines of complex code...
   }
}

Refaktorieren Sie dies, indem Sie ein Validatorobjekt einführen.

public class SystemUnderTest
{
    public void DoStuff()
    {
       // Blah
       validator.Invoke(..)
    }
}

Jetzt müssen wir nur noch testen, ob der Validator korrekt aufgerufen wurde. Der tatsächliche Validierungsprozess (die zuvor private Logik) kann in reiner Isolation getestet werden. Es ist keine komplexe Testeinrichtung erforderlich, um sicherzustellen, dass diese Validierung erfolgreich ist.


1
Ich persönlich denke, dass SRP zu weit gehen kann. Wenn ich beispielsweise einen Kontodienst schreibe, der sich mit dem Aktualisieren / Erstellen / Löschen von Konten in einer Anwendung befasst, sagen Sie mir, dass Sie für jede Operation eine separate Klasse erstellen sollten? Und was ist zum Beispiel mit der Validierung? Lohnt es sich wirklich, die Validierungslogik in eine andere Klasse zu extrahieren, wenn sie nirgendwo anders verwendet wird? imo das macht Code nur weniger lesbar, weniger prägnant und packt Ihre Anwendung mit überflüssigen Klassen!
JCVANDAN

1
Es hängt alles vom Szenario ab. Die Definition einer Person, "eine Sache zu tun", kann sich von der einer anderen Person unterscheiden. In diesem Fall ist der private Code, den Sie testen möchten, eindeutig komplex / schwierig einzurichten. Die Tatsache, dass Sie es testen möchten, beweist, dass es zu viel tut. Daher ist es ideal, ein neues Objekt zu haben.
Finglas

1
Ich bin nicht der Meinung, dass Ihr Code weniger lesbar ist. Eine Klasse zur Validierung zu haben ist einfacher zu lesen als die Validierung in das andere Objekt eingebettet zu haben. Sie haben immer noch die gleiche Menge an Code, er ist nur nicht in einer großen Klasse verschachtelt. Ich hätte lieber mehrere Klassen, die eine Sache sehr gut machen, als eine große Klasse.
Finglas

4
@dormisher betrachtet Ihren SRP in Bezug auf Ihren Kontodienst als "einzigen Grund für eine Änderung". Wenn sich Ihre CRUD-Operationen natürlich zusammen ändern würden, würden sie zum SRP passen. Normalerweise würden Sie diese ändern, da sich das Datenbankschema möglicherweise ändert, was sich natürlich auf das Einfügen und Aktualisieren auswirkt (obwohl dies möglicherweise nicht immer gelöscht wird).
Anthony Pegram

@finglas was denkst du darüber: stackoverflow.com/questions/29354729/… . Ich habe keine Lust, die private Methode zu testen, also sollte ich sie vielleicht nicht in eine Klasse aufteilen, aber sie wird in zwei anderen öffentlichen Methoden verwendet, sodass ich am Ende zweimal dieselbe Logik testen würde.
Rodrigo Ruiz

13

Einige gute Antworten. Eine Sache, die ich nicht erwähnt habe, ist, dass bei der testgetriebenen Entwicklung (TDD) private Methoden während der Refactoring-Phase erstellt werden (siehe Extraktionsmethode für ein Beispiel für ein Refactoring-Muster) und daher bereits über die erforderliche Testabdeckung verfügen sollten . Wenn Sie es richtig machen (und natürlich erhalten Sie eine gemischte Menge an Meinungen, wenn es um Korrektheit geht), sollten Sie sich keine Sorgen machen müssen, dass Sie eine private Methode veröffentlichen müssen, nur damit Sie sie testen können.


11

Warum nicht den Stack-Management-Algorithmus in eine Utility-Klasse aufteilen? Die Utility-Klasse kann die Stapel verwalten und öffentliche Accessoren bereitstellen. Die Unit-Tests können sich auf Implementierungsdetails konzentrieren. Tiefe Tests für algorithmisch knifflige Klassen sind sehr hilfreich, um Randfälle zu falten und die Abdeckung sicherzustellen.

Dann kann Ihre aktuelle Klasse sauber an die Utility-Klasse delegieren, ohne Implementierungsdetails offenzulegen. Die Tests beziehen sich auf die Paginierungsanforderungen, die von anderen empfohlen wurden.


10

In Java gibt es auch die Möglichkeit, das Paket privat zu machen (dh den Sichtbarkeitsmodifikator wegzulassen). Wenn sich Ihre Komponententests im selben Paket wie die getestete Klasse befinden, sollte sie diese Methoden sehen können und ist etwas sicherer, als die Methode vollständig öffentlich zu machen.


10

Private Methoden werden normalerweise als "Hilfsmethoden" verwendet. Daher geben sie nur Grundwerte zurück und arbeiten niemals mit bestimmten Instanzen von Objekten.

Sie haben einige Optionen, wenn Sie sie testen möchten.

  • Verwenden Sie Reflexion
  • Geben Sie dem Methodenpaket Zugriff

Alternativ können Sie eine neue Klasse mit der Hilfsmethode als öffentliche Methode erstellen, wenn sie für eine neue Klasse ausreichend ist.

Hierzu gibt es einen sehr guten Artikel .


1
Fairer Kommentar. War nur eine Option, vielleicht eine schlechte.
Adamjmarkham

@Finglas Warum ist das Testen von privatem Code mithilfe von Reflektion eine schlechte Idee?
Anshul

@Anshul private Methoden sind Implementierungsdetails und kein Verhalten. Mit anderen Worten, sie ändern sich. Namen ändern sich. Parameter ändern sich. Inhalt ändert sich. Dies bedeutet, dass diese Tests die ganze Zeit unterbrochen werden. Öffentliche Methoden hingegen sind in Bezug auf die API weitaus stabiler und daher widerstandsfähiger. Wenn Sie Ihre Tests mit öffentlichen Methoden durchführen, überprüfen Sie das Verhalten und nicht die Implementierungsdetails. Sie sollten gut sein.
Finglas

1
@Finglas Warum möchten Sie die Implementierungsdetails nicht testen? Es ist Code, der funktionieren muss, und wenn er sich häufiger ändert, erwarten Sie, dass er häufiger beschädigt wird. Dies ist ein Grund mehr, ihn mehr zu testen als die öffentliche API. Nachdem Ihre Codebasis stabil ist, nehmen wir an, dass irgendwann in der Zukunft jemand vorbeikommt und eine private Methode ändert, die die interne Funktionsweise Ihrer Klasse beeinträchtigt. Ein Test, der die Interna Ihrer Klasse testet, würde ihnen sofort sagen, dass sie etwas kaputt gemacht haben. Ja, es würde die Wartung Ihrer Tests erschweren, aber das ist etwas, was Sie von Tests mit vollständiger Abdeckung erwarten.
Anshul

1
@Finglas Das Testen der Implementierungsdetails ist nicht falsch. Das ist Ihre Haltung dazu, aber dies ist ein viel diskutiertes Thema. Das Testen der Interna bietet Ihnen einige Vorteile, auch wenn sie möglicherweise nicht für eine vollständige Abdeckung erforderlich sind. Ihre Tests sind fokussierter, da Sie die privaten Methoden direkt testen, anstatt die öffentliche API zu durchlaufen, was Ihren Test möglicherweise in Konflikt bringt. Negative Tests sind einfacher, da Sie die privaten Mitglieder manuell ungültig machen und sicherstellen können, dass die öffentliche API als Reaktion auf den inkonsistenten internen Status entsprechende Fehler zurückgibt. Es gibt weitere Beispiele.
Anshul

9

Wenn Sie C # verwenden, können Sie die Methode intern machen. Auf diese Weise verschmutzen Sie die öffentliche API nicht.

Fügen Sie dann der DLL ein Attribut hinzu

[Assembly: InternalsVisibleTo ("MyTestAssembly")]

Jetzt sind alle Methoden in Ihrem MyTestAssembly-Projekt sichtbar. Vielleicht nicht perfekt, aber besser, als private Methoden öffentlich zu machen, nur um sie zu testen.


7

Verwenden Sie Reflection, um bei Bedarf auf die privaten Variablen zuzugreifen.

Aber wirklich, Sie interessieren sich nicht für den internen Status der Klasse, Sie möchten nur testen, ob die öffentlichen Methoden das zurückgeben, was Sie in den Situationen erwarten, die Sie erwarten können.


Was würden Sie mit einer Void-Methode machen?
IntelliData

@IntelliData Verwenden Sie Reflection, um bei Bedarf auf die privaten Variablen zuzugreifen.
Daniel Alexiuc

6

Ich würde sagen, es ist eine schlechte Idee, denn ich bin mir nicht sicher, ob Sie irgendwann Vorteile und potenzielle Probleme haben. Wenn Sie den Vertrag eines Aufrufs ändern, nur um eine private Methode zu testen, testen Sie die Klasse nicht in ihrer Verwendung, sondern erstellen ein künstliches Szenario, das Sie nie beabsichtigt hatten.

Wenn Sie die Methode als öffentlich deklarieren, können Sie in sechs Monaten (nachdem Sie vergessen haben, dass der einzige Grund für die Veröffentlichung einer Methode das Testen ist), dass Sie (oder wenn Sie das Projekt übergeben haben) eine völlig andere Person gewonnen haben Verwenden Sie es nicht, was zu möglicherweise unbeabsichtigten Folgen und / oder einem Wartungsalptraum führen kann.


1
was ist, wenn die Klasse die Implementierung eines Service - Vertrag ist , und es wird immer nur über sie die Schnittstelle - auf diese Weise , selbst wenn die private Methode veröffentlicht wurde, wird es sowieso nicht genannt werden , da sie nicht sichtbar
jcvandan

Ich würde die Klasse immer noch mit der öffentlichen API (Schnittstelle) testen wollen, da Sie sonst die Tests ändern müssten, wenn Sie die Klasse umgestalten würden, um die interne Methode aufzuteilen, was bedeutet, dass Sie einige ausgeben müssten Bemühen Sie sich, sicherzustellen, dass die neuen Tests genauso funktionieren wie die alten Tests.
Beny23

Wenn die einzige Möglichkeit, um sicherzustellen, dass die Testabdeckung jeden Randfall innerhalb einer privaten Methode umfasst, darin besteht, sie direkt aufzurufen, kann dies darauf hinweisen, dass die Klassenkomplexität eine Umgestaltung rechtfertigt, um sie zu vereinfachen.
Beny23

@dormisher - Wenn die Klasse immer nur über die Schnittstelle verwendet wird, müssen Sie nur testen, ob sich die öffentlichen Schnittstellenmethoden korrekt verhalten. Wenn die öffentlichen Methoden das tun, was sie tun sollen, warum interessieren Sie sich dann für die privaten?
Andrzej Doyle

@Andrzej - Ich denke, ich sollte es nicht wirklich tun, aber es gibt Situationen, in denen ich glaube, dass es gültig sein könnte, z. B. eine private Methode, die den Modellstatus validiert. Es kann sich lohnen, eine Methode wie diese zu testen - aber eine Methode wie diese sollte über testbar sein die öffentliche Schnittstelle sowieso
jcvandan

6

In Bezug auf Unit-Tests sollten Sie definitiv keine weiteren Methoden hinzufügen. Ich glaube, Sie sollten besser einen Testfall über Ihre first()Methode erstellen, der vor jedem Test aufgerufen wird. - dann können Sie mehrere Male den Anruf next(), previous()und last()zu sehen , ob die Ergebnisse Ihren Erwartungen entsprechen. Ich denke, wenn Sie Ihrer Klasse keine weiteren Methoden hinzufügen (nur zu Testzwecken), würden Sie sich an das "Black Box" -Prinzip des Testens halten.



5

In Ihrem Update sagen Sie, dass es gut ist, nur mit der öffentlichen API zu testen. Hier gibt es eigentlich zwei Schulen.

  1. Black-Box-Test

    Die Black-Box-Schule sagt, dass die Klasse als Black-Box betrachtet werden sollte, die niemand in der Implementierung sehen kann. Der einzige Weg, dies zu testen, ist über die öffentliche API - genau wie die Benutzer der Klasse sie verwenden werden.

  2. White-Box-Test.

    Die White-Box-Schule hält es für selbstverständlich, das Wissen über die Implementierung der Klasse zu nutzen und die Klasse dann zu testen, um zu wissen, dass sie ordnungsgemäß funktioniert.

Ich kann mich wirklich nicht an der Diskussion beteiligen. Ich dachte nur, es wäre interessant zu wissen, dass es zwei verschiedene Möglichkeiten gibt, eine Klasse (oder eine Bibliothek oder was auch immer) zu testen.


4

Es gibt tatsächlich Situationen, in denen Sie dies tun sollten (z. B. wenn Sie einige komplexe Algorithmen implementieren). Tun Sie es einfach paketprivat und das wird ausreichen. Aber in den meisten Fällen haben Sie wahrscheinlich zu komplexe Klassen, was das Ausklammern der Logik in andere Klassen erfordert.


4

Private Methoden, die Sie isoliert testen möchten, sind ein Hinweis darauf, dass in Ihrer Klasse ein anderes "Konzept" vergraben ist. Extrahieren Sie dieses "Konzept" in eine eigene Klasse und testen Sie es als separate "Einheit".

Schauen Sie sich dieses Video an, um eine wirklich interessante Sicht auf das Thema zu erhalten.


4

Sie sollten niemals zulassen, dass Ihre Tests Ihren Code diktieren. Ich spreche nicht über TDD oder andere DDs, genau das, was Sie fragen. Benötigt Ihre App diese Methoden, um öffentlich zu sein? Wenn ja, testen Sie sie. Wenn dies nicht der Fall ist, machen Sie sie nicht nur zum Testen öffentlich. Gleiches gilt für Variablen und andere. Lassen Sie die Anforderungen Ihrer Anwendung den Code bestimmen und Ihre Tests testen, ob die Anforderungen erfüllt sind. (Wiederum meine ich nicht, zuerst zu testen oder nicht, ich meine, eine Klassenstruktur zu ändern, um ein Testziel zu erreichen).

Stattdessen sollten Sie "höher testen". Testen Sie die Methode, die die private Methode aufruft. Ihre Tests sollten jedoch Ihre Anwendungsanforderungen testen und nicht Ihre "Implementierungsentscheidungen".

Zum Beispiel (Bod Pseudo Code hier);

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

Es gibt keinen Grund, "Hinzufügen" zu testen. Sie können stattdessen "Bücher" testen.

Lassen Sie Ihre Tests niemals Entscheidungen zum Code-Design für Sie treffen. Testen Sie, ob Sie das erwartete Ergebnis erhalten, nicht wie Sie dieses Ergebnis erhalten.


1
"Lassen Sie Ihre Tests niemals Entscheidungen zum Code-Design für Sie treffen" - ich muss nicht zustimmen. Schwer zu testen ist ein Code-Geruch. Wenn Sie es nicht testen können, woher wissen Sie, dass es nicht kaputt ist?
Alfred Armstrong

Zur Verdeutlichung stimme ich zu, dass Sie beispielsweise das externe API-Design einer Bibliothek nicht ändern sollten. Möglicherweise müssen Sie jedoch eine Umgestaltung vornehmen, um die Testbarkeit zu verbessern.
Alfred Armstrong

Ich habe festgestellt, dass wenn Sie zum Testen einen Refactor benötigen, dies nicht der Codegeruch ist, über den Sie sich Sorgen machen sollten. Du hast noch etwas falsches. Das ist natürlich eine persönliche Erfahrung, und wir könnten beide wahrscheinlich mit soliden Daten in beide Richtungen streiten. Ich hatte einfach noch nie "schwer zu testen", was überhaupt kein "schlechtes Design" war.
Coteyr

Diese Lösung eignet sich für eine Methode wie Bücher , die einen Wert zurückgibt. Sie können den Rückgabetyp in einer Zusicherung überprüfen . Aber wie würden Sie vorgehen, um eine ungültige Methode zu überprüfen ?
IntelliData

Void-Methoden tun etwas oder müssen nicht existieren. Überprüfen Sie, was sie tun,
Coteyr

2

Ich stimme eher zu, dass die Boni für das Testen der Einheit die Probleme überwiegen, die Sichtbarkeit einiger Mitglieder zu erhöhen. Eine leichte Verbesserung besteht darin, es geschützt und virtuell zu machen und es dann in einer Testklasse zu überschreiben, um es verfügbar zu machen.

Wenn es sich um eine Funktionalität handelt, die Sie separat testen möchten, schlägt dies nicht ein fehlendes Objekt in Ihrem Design vor? Vielleicht könnten Sie es in eine separate testbare Klasse einordnen ... dann delegiert Ihre vorhandene Klasse nur an eine Instanz dieser neuen Klasse.


2

Ich behalte die Testklassen im Allgemeinen im selben Projekt / in derselben Assembly wie die zu testenden Klassen.
Auf diese Weise brauche ich nurinternal Sichtbarkeit, um Funktionen / Klassen testbar zu machen.

Dies verkompliziert Ihren Bauprozess etwas, der die Testklassen herausfiltern muss. Dies erreiche ich, indem ich alle meine Testklassen benenneTestedClassTest Testklassen benenne und diese Klassen mit einem regulären Ausdruck filtere.

Dies gilt natürlich nur für den C # / .NET-Teil Ihrer Frage


2

Ich werde oft eine Methode namens etwas wie hinzufügen validate, verify, check, usw., zu einer Klasse , so dass er den internen Zustand eines Objekts zu testen aufgerufen werden kann.

Manchmal wird diese Methode in einen ifdef-Block eingeschlossen (ich schreibe hauptsächlich in C ++), damit sie nicht für die Veröffentlichung kompiliert wird. In Releases ist es jedoch häufig hilfreich, Validierungsmethoden bereitzustellen, mit denen die Objektbäume des Programms überprüft werden.


2

Guava verfügt über eine Annotation @VisibleForTesting zum Markieren von Methoden, deren Umfang (Paket oder öffentlich) erweitert wurde, als dies sonst der Fall wäre. Ich verwende eine @ Private-Annotation für dieselbe Sache.

Während die öffentliche API getestet werden muss, ist es manchmal bequem und sinnvoll, an Dinge heranzukommen, die normalerweise nicht öffentlich sind.

Wann:

  • Eine Klasse wird insgesamt erheblich weniger lesbar gemacht, indem sie in mehrere Klassen aufgeteilt wird.
  • nur um es testbarer zu machen,
  • und ein Testzugriff auf die Innereien würde den Trick tun

Es scheint, als ob Religion die Technik übertrumpft.


2

Normalerweise lasse ich diese Methoden als protectedund platziere den Komponententest im selben Paket (aber in einem anderen Projekt oder Quellordner), wo sie auf alle geschützten Methoden zugreifen können, da der Klassenlader sie im selben Namespace platziert.


2

Nein, denn es gibt bessere Möglichkeiten, diese Katze zu häuten.

Einige Unit-Test-Kabelbäume basieren auf Makros in der Klassendefinition, die automatisch erweitert werden, um im Testmodus Hooks zu erstellen. Sehr C-Stil, aber es funktioniert.

Eine einfachere OO-Redewendung besteht darin, alles, was Sie testen möchten, "geschützt" anstatt "privat" zu machen. Das Testkabel erbt von der zu testenden Klasse und kann dann auf alle geschützten Mitglieder zugreifen.

Oder Sie wählen die Option "Freund". Persönlich ist dies die Funktion von C ++, die ich am wenigsten mag, weil sie gegen die Kapselungsregeln verstößt, aber es ist zufällig notwendig, wie C ++ einige Funktionen implementiert, also hey ho.

Wenn Sie Unit-Tests durchführen, müssen Sie diesen Mitgliedern wahrscheinlich auch Werte hinzufügen. White-Box-SMS ist vollkommen gültig. Und das würde Ihre Kapselung wirklich zerstören.


2

In .Net gibt es eine spezielle Klasse namens PrivateObjectdeigned, mit der Sie auf private Methoden einer Klasse zugreifen können.

Weitere Informationen finden Sie im MSDN oder hier im Stapelüberlauf

(Ich frage mich, dass es bisher niemand erwähnt hat.)

Es gibt Situationen, in denen dies nicht ausreicht. In diesem Fall müssen Sie Reflexion verwenden.

Trotzdem würde ich mich an die allgemeine Empfehlung halten, keine privaten Methoden zu testen, aber wie immer gibt es immer Ausnahmen.


1

Wie in den Kommentaren anderer ausführlich erwähnt, sollten sich Unit-Tests auf die öffentliche API konzentrieren. Abgesehen von Vor- und Nachteilen und Begründungen können Sie private Methoden in einem Komponententest mithilfe von Reflexion aufrufen. Sie müssten natürlich sicherstellen, dass Ihre JRE-Sicherheit dies zulässt. Das Aufrufen privater Methoden wird vom Spring Framework mit seinen ReflectionUtils verwendet (siehe diemakeAccessible(Method) Methode).

Hier ist eine kleine Beispielklasse mit einer privaten Instanzmethode.

public class A {
    private void doSomething() {
        System.out.println("Doing something private.");
    }
}

Und eine Beispielklasse, die die Methode der privaten Instanz ausführt.

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class B {
    public static final void main(final String[] args) {
        try {
            Method doSomething = A.class.getDeclaredMethod("doSomething");
            A o = new A();
            //o.doSomething(); // Compile-time error!
            doSomething.setAccessible(true); // If this is not done, you get an IllegalAccessException!
            doSomething.invoke(o);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (SecurityException e) {
            e.printStackTrace();
        }
    }
}

Doing something private. Wenn Sie B ausführen, wird gedruckt. Wenn Sie es wirklich benötigen, kann Reflection in Komponententests verwendet werden, um auf Methoden für private Instanzen zuzugreifen.


0

Persönlich habe ich die gleichen Probleme beim Testen privater Methoden, und dies liegt daran, dass einige der Testtools begrenzt sind. Es ist nicht gut, wenn Ihr Design von begrenzten Werkzeugen angetrieben wird, wenn diese nicht auf Ihre Bedürfnisse reagieren. Ändern Sie das Werkzeug und nicht das Design. Da Sie nach C # fragen, kann ich keine guten Testtools vorschlagen, aber für Java gibt es zwei leistungsstarke Tools: TestNG und PowerMock, und Sie können entsprechende Testtools für die .NET-Plattform finden

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.