Unit Test Private Methode in C ++ mit einer Friend-Klasse

15

Ich weiß, dass dies eine umstrittene Praxis ist, aber nehmen wir an, dass dies die beste Option für mich ist. Ich frage mich, was ist die eigentliche Technik, um dies zu tun. Der Ansatz, den ich sehe, ist folgender:

1) Machen Sie eine Freundesklasse zu der Klasse, deren Methode ich testen möchte.

2) Erstellen Sie in der Friend-Klasse eine oder mehrere öffentliche Methoden, die die privaten Methoden der getesteten Klasse aufrufen.

3) Testen Sie die öffentlichen Methoden der Friend-Klasse.

Hier ist ein einfaches Beispiel, um die obigen Schritte zu veranschaulichen:

#include <iostream>

class MyClass
{
  friend class MyFriend; // Step 1

  private:
  int plus_two(int a)
  {
    return a + 2;
  }
};

class MyFriend
{
public:
  MyFriend(MyClass *mc_ptr_1)
  {
    MyClass *mc_ptr = mc_ptr_1;
  }

  int plus_two(int a) // Step 2
  {
    return mc_ptr->plus_two(a);
  }
private:
  MyClass *mc_ptr;
};

int main()
{
  MyClass mc;
  MyFriend mf(&mc);
  if (mf.plus_two(3) == 5) // Step 3
    {
      std::cout << "Passed" << std::endl;
    }
  else
    {
      std::cout << "Failed " << std::endl;
    }

  return 0;
}

Bearbeiten:

Ich sehe, dass sich in der folgenden Diskussion eine der Antworten über meine Codebasis wundert.

Meine Klasse verfügt über Methoden, die von anderen Methoden aufgerufen werden. Keine dieser Methoden sollte außerhalb der Klasse aufgerufen werden, daher sollten sie privat sein. Natürlich könnten sie zu einer Methode zusammengefasst werden, aber logischerweise sind sie viel besser voneinander getrennt. Diese Methoden sind kompliziert genug, um Einheitentests zu rechtfertigen, und aufgrund von Leistungsproblemen muss ich diese Methoden sehr wahrscheinlich neu faktorisieren. Daher wäre es schön, einen Test zu haben, um sicherzustellen, dass mein Re-Factoring nichts kaputt macht. Ich bin nicht der einzige, der im Team arbeitet, obwohl ich der einzige bin, der an diesem Projekt arbeitet, einschließlich der Tests.

Nach alledem ging es bei meiner Frage nicht darum, ob es eine gute Praxis ist, Komponententests für private Methoden zu schreiben, obwohl ich das Feedback schätze.

Akavall
quelle
5
Hier ist ein fragwürdiger Vorschlag für eine fragwürdige Frage. Mir gefällt die Kopplung von Freund nicht, da der dann veröffentlichte Code über den Test Bescheid wissen muss. Nirs Antwort unten ist eine Möglichkeit, das zu lindern, aber ich mag es immer noch nicht, die Klasse zu ändern, um sie dem Test anzupassen. Da ich mich nicht oft auf die Vererbung verlasse, mache ich manchmal einfach die ansonsten privaten Methoden geschützt und lasse eine Testklasse nach Bedarf erben und verfügbar machen. Ich erwarte für diesen Kommentar mindestens drei "Knurren", aber in Wirklichkeit können sich die öffentliche API und die Test-API von der privaten API unterscheiden und unterscheiden. Meh.
J Trana
4
@JTrana: Warum schreibst du das nicht als richtige Antwort auf?
Bart van Ingen Schenau
In Ordnung. Dies ist nicht einer von denen, auf die Sie sehr stolz sind, aber hoffentlich hilft es.
J Trana

Antworten:

23

Eine Alternative zu Freunden, die ich häufig benutze, ist ein Muster, das ich als access_by kenne. Es ist ziemlich einfach:

class A {
  void priv_method(){};
 public:
  template <class T> struct access_by;
  template <class T> friend struct access_by;
}

Angenommen, Klasse B ist am Testen von A beteiligt. Sie können Folgendes schreiben:

template <> struct access_by<B> {
  call_priv_method(A & a) {a.priv_method();}
}

Sie können dann diese Spezialisierung von access_by verwenden, um private Methoden von A aufzurufen. Im Grunde bedeutet dies, dass Sie die Freundschaft in die Header-Datei der Klasse eintragen, die die privaten Methoden von A aufrufen möchte. Sie können auch Freunde zu A hinzufügen, ohne die Quelle von A zu ändern. Idiomatisch zeigt es auch an, wer die Quelle von A liest, dass A B keinen wahren Freund im Sinne einer Erweiterung seiner Schnittstelle anzeigt. Vielmehr ist die Schnittstelle von A vollständig und B benötigt einen speziellen Zugriff auf A (Tests sind ein gutes Beispiel, ich habe dieses Muster auch bei der Implementierung von Boost-Python-Bindungen verwendet, manchmal ist eine Funktion, die in C ++ privat sein muss, praktisch in die Python-Ebene für die Implementierung).

Nir Friedman
quelle
Neugierig auf einen gültigen Anwendungsfall, um das zu machen friend access_by, ist nicht der erste Nicht-Freund ausreichend - als verschachtelte Struktur hätte er Zugriff auf alles in A? z.B. coliru.stacked-crooked.com/a/663dd17ed2acd7a3
scharf
10

Wenn es schwer zu testen ist, ist es schlecht geschrieben

Wenn Sie eine Klasse mit privaten Methoden haben, die komplex genug sind, um ihren eigenen Test zu rechtfertigen, tut die Klasse zu viel. Drinnen ist eine andere Klasse, die versucht, rauszukommen.

Extrahieren Sie die privaten Methoden, die Sie testen möchten, in eine neue Klasse (oder Klassen) und machen Sie sie öffentlich. Testen Sie die neuen Klassen.

Dieses Refactoring erleichtert nicht nur das Testen des Codes, sondern auch das Verständnis und die Pflege des Codes.

Kevin Cline
quelle
1
Ich stimme dieser Antwort vollkommen zu. Wenn Sie Ihre privaten Methoden nicht vollständig durch Testen der öffentlichen Methoden testen können, stimmt etwas nicht, und eine Umgestaltung der privaten Methoden in die eigene Klasse wäre eine gute Lösung.
David Perfors
4
In meiner Codebasis hat eine Klasse eine sehr komplexe Methode, mit der ein Rechengraph initialisiert wird. Es werden mehrere Unterfunktionen nacheinander aufgerufen, um verschiedene Aspekte davon zu erreichen. Jede Unterfunktion ist ziemlich kompliziert und die Gesamtsumme des Codes ist sehr kompliziert. Die Unterfunktionen sind jedoch bedeutungslos, wenn sie nicht in dieser Klasse und in der richtigen Reihenfolge aufgerufen werden. Alles, was den Benutzer interessiert, ist die vollständige Initialisierung des Rechengraphen. Die Zwischenprodukte sind für den Benutzer wertlos. Bitte, ich würde gerne hören, wie ich das umgestalten soll und warum es sinnvoller ist, als nur die privaten Methoden zu testen.
Nir Friedman
1
@Nir: Machen Sie das Triviale: Extrahieren Sie eine Klasse mit all diesen Methoden und machen Sie Ihre vorhandene Klasse zu einer Fassade um die neue Klasse.
Kevin Cline
Diese Antwort ist richtig, hängt aber wirklich von den Daten ab, mit denen Sie arbeiten. In meinem Fall werden mir keine tatsächlichen Testdaten zur Verfügung gestellt, daher muss ich einige erstellen, indem ich Echtzeitdaten beobachte und sie dann in meine Anwendung "einspeisen" und sehen, wie Ein einzelnes Datenelement ist zu komplex, um es zu handhaben. Daher ist es viel einfacher, Teilprüfdaten künstlich zu erstellen, die nur eine Teilmenge der zu zielenden Echtzeitdaten sind, als die Echtzeitdaten tatsächlich zu reproduzieren. Die privaten Funktionen sind nicht komplex genug, um die Implementierung mehrerer anderer kleiner Klassen (mit der entsprechenden Funktionalität) zu
fordern
4

Sie sollten keine privaten Methoden testen. Zeitraum. Die Klassen, die Ihre Klasse verwenden, kümmern sich nur um die Methoden, die sie bereitstellen, nicht um die, die sie unter der Haube verwenden, um zu arbeiten.

Wenn Sie sich Gedanken über die Codeabdeckung machen, müssen Sie Konfigurationen finden, mit denen Sie diese private Methode über einen der öffentlichen Methodenaufrufe testen können. Wenn Sie das nicht können, wozu ist die Methode dann überhaupt gut? Es ist einfach nicht erreichbarer Code.

Ampt
quelle
6
Der Sinn privater Methoden besteht darin, die Entwicklung zu vereinfachen (durch Trennung von Bedenken oder Aufrechterhaltung von DRY oder einer beliebigen Anzahl von Dingen), sie sollen jedoch nicht dauerhaft sein. Sie sind aus diesem Grund privat. Sie können von einer Implementierung zur nächsten drastisch erscheinen, verschwinden oder ihre Funktionalität ändern, weshalb es nicht immer praktisch oder sogar nützlich ist, sie an Komponententests zu binden.
Ampt
8
Es mag nicht immer praktisch oder nützlich sein, aber das ist weit davon entfernt zu sagen, dass Sie sie niemals testen sollten. Sie sprechen von den privaten Methoden, als wären sie die privaten Methoden eines anderen. "Sie könnten erscheinen, verschwinden ...". Nein, das konnten sie nicht. Wenn Sie sie direkt testen, sollte dies nur daran liegen, dass Sie sie selbst warten. Wenn Sie die Implementierung ändern, ändern Sie die Tests. Kurz gesagt, Ihre pauschale Aussage ist nicht gerechtfertigt. Obwohl es gut ist, das OP davor zu warnen, ist seine Frage immer noch berechtigt und Ihre Antwort beantwortet sie nicht wirklich.
Nir Friedman
2
Lassen Sie mich auch bemerken: Der OP sagte, er sei sich bewusst, dass es sich um eine debattierte Praxis handelt. Also, wenn er es trotzdem machen will, hat er vielleicht wirklich gute Gründe dafür? Keiner von uns kennt die Details seiner Codebasis. In dem Code, mit dem ich arbeite, haben wir einige sehr erfahrene und erfahrene Programmierer, und sie hielten es in einigen Fällen für nützlich, private Methoden in einem Unit-Test zu testen.
Nir Friedman
2
-1, eine Antwort wie "Sie sollten keine privaten Methoden testen." ist meiner Meinung nach nicht hilfreich. Das Thema, wann private Methoden zu testen sind und wann nicht, wurde auf dieser Site ausreichend erörtert. Das OP hat gezeigt, dass er sich dieser Diskussion bewusst ist, und es ist klar, dass er nach Lösungen sucht, unter der Annahme, dass in seinem Fall das Testen privater Methoden der richtige Weg ist.
Doc Brown
3
Ich denke, das Problem hier ist, dass dies ein sehr klassisches XY-Problem sein könnte , da OP glaubt, er müsse seine privaten Methoden aus dem einen oder anderen Grund testen, wenn er sich in Wirklichkeit den Tests aus einem pragmatischeren Blickwinkel nähern könnte und das Private ansieht Methoden als bloße Hilfsfunktionen für die öffentlichen, die der Vertrag mit den Endbenutzern der Klasse ist.
Ampt
3

Hierfür gibt es einige Optionen. Bedenken Sie jedoch, dass sie (im Wesentlichen) die öffentliche Schnittstelle Ihrer Module ändern, um Ihnen Zugriff auf interne Implementierungsdetails zu gewähren (und Unit-Tests effektiv in eng gekoppelte Client-Abhängigkeiten umzuwandeln, sofern dies erforderlich ist) überhaupt keine Abhängigkeiten).

  • Sie können der getesteten Klasse eine Friend-Deklaration (Klasse oder Funktion) hinzufügen.

  • Sie können #define private publicden Anfang Ihrer Testdateien anfügen , bevor Sie #includeden getesteten Code eingeben. Falls der getestete Code eine bereits kompilierte Bibliothek ist, können die Header möglicherweise nicht mehr mit dem bereits kompilierten Binärcode übereinstimmen (und UB verursachen).

  • Sie können ein Makro in Ihre getestete Klasse einfügen und zu einem späteren Zeitpunkt entscheiden, was dieses Makro bedeutet (mit einer anderen Definition für das Testen von Code). Auf diese Weise können Sie Interna testen, aber Sie können auch Client-Code von Drittanbietern in Ihre Klasse hacken (indem Sie in der hinzugefügten Deklaration eine eigene Definition erstellen).

utnapistim
quelle
2

Hier ist ein fragwürdiger Vorschlag für eine fragwürdige Frage. Ich mag das Koppeln von Freunden nicht, da der dann veröffentlichte Code über den Test Bescheid wissen muss. Nirs Antwort ist eine Möglichkeit, das zu lindern, aber ich mag es immer noch nicht, die Klasse so zu ändern, dass sie dem Test entspricht.

Da ich mich nicht oft auf die Vererbung verlasse, mache ich manchmal einfach die ansonsten privaten Methoden geschützt und lasse eine Testklasse nach Bedarf erben und verfügbar machen. Die Realität ist, dass sich die öffentliche API und die Test-API von der privaten API unterscheiden können, was zu einer Art Bindung führt.

Hier ist ein praktisches Beispiel, bei dem ich auf diesen Trick zurückgreife. Ich schreibe eingebetteten Code und wir verlassen uns ziemlich oft auf Zustandsautomaten. Die externe API muss nicht unbedingt über den internen Status der Zustandsmaschine informiert sein, der Test sollte jedoch (möglicherweise) die Konformität mit dem Zustandsmaschinendiagramm im Designdokument überprüfen. Ich könnte einen "Current State" Getter als geschützt aussetzen und dann den Testzugriff darauf gewähren, so dass ich die Zustandsmaschine vollständiger testen kann. Ich finde es oft schwierig, diese Art von Klasse als Black Box zu testen.

J Trana
quelle
Obwohl es sich um einen Java-Ansatz handelt, ist es durchaus üblich, private Funktionen als Standardstufe festzulegen, damit andere Klassen im selben Paket (die Testklassen) sie sehen können.
0

Sie können Ihren Code mit vielen Problemumgehungen schreiben, um zu verhindern, dass Sie Freunde verwenden müssen.

Sie können Klassen schreiben und haben überhaupt keine privaten Methoden. Alles, was Sie dann tun müssen, ist Implementierungsfunktionen innerhalb der Kompilierungseinheit vorzunehmen, Ihre Klasse sie aufrufen zu lassen und alle Datenelemente weiterzuleiten, auf die sie zugreifen müssen.

Und ja, das bedeutet, dass Sie die Signaturen ändern oder neue "Implementierungs" -Methoden hinzufügen können, ohne Ihren Header in Zukunft zu ändern.

Sie müssen abwägen, ob es sich lohnt oder nicht. Und eine Menge wird wirklich davon abhängen, wer Ihren Header sehen wird.

Wenn ich eine Bibliothek eines Drittanbieters verwende, möchte ich lieber keine Freundeserklärungen für ihre Unit-Tester sehen. Ich möchte auch nicht ihre Bibliothek bauen und ihre Tests laufen lassen, wenn ich das tue. Leider tun das zu viele Open-Source-Bibliotheken von Drittanbietern, die ich erstellt habe.

Testen ist Aufgabe der Autoren der Bibliothek, nicht der Benutzer.

Es sind jedoch nicht alle Klassen für den Benutzer Ihrer Bibliothek sichtbar. Viele Klassen sind "Implementierungen" und Sie implementieren sie am besten, um sicherzustellen, dass sie ordnungsgemäß funktionieren. In diesen haben Sie möglicherweise noch private Methoden und Mitglieder, möchten aber, dass Unit-Tester sie testen. Gehen Sie also so vor, wenn dies schneller zu robustem Code führt, der für diejenigen, die dies tun müssen, einfach zu warten ist.

Wenn sich die Benutzer Ihrer Klasse alle in Ihrem eigenen Unternehmen oder Team befinden, können Sie diese Strategie auch etwas lockerer gestalten, sofern dies nach den Kodierungsstandards Ihres Unternehmens zulässig ist.

Goldesel
quelle