Ich arbeite an einem Projekt, in dem wir ein neues Modul implementieren und testen müssen. Ich hatte eine ziemlich klare Architektur im Sinn, also schrieb ich schnell die Hauptklassen und -methoden auf und dann begannen wir, Unit-Tests zu schreiben.
Während des Schreibens der Tests mussten wir einige Änderungen am ursprünglichen Code vornehmen, wie z
- Private Methoden öffentlich machen, um sie zu testen
- Hinzufügen zusätzlicher Methoden für den Zugriff auf private Variablen
- Hinzufügen zusätzlicher Methoden zum Einfügen von Scheinobjekten, die verwendet werden sollten, wenn der Code in einem Komponententest ausgeführt wird.
Irgendwie habe ich das Gefühl, dass dies Symptome sind, dass wir etwas falsch machen, z
- Das ursprüngliche Design war falsch (einige Funktionen sollten von Anfang an öffentlich sein).
- Der Code wurde nicht richtig für die Schnittstelle zu Unit-Tests entwickelt (möglicherweise aufgrund der Tatsache, dass wir mit dem Design der Unit-Tests begonnen haben, als bereits einige Klassen entworfen wurden).
- Wir implementieren Unit-Tests falsch (z. B. sollten Unit-Tests nur die öffentlichen Methoden einer API direkt testen / ansprechen, nicht die privaten).
- eine Mischung aus den oben genannten drei Punkten und vielleicht einigen zusätzlichen Themen, über die ich nicht nachgedacht habe.
Da ich einige Erfahrungen mit Unit-Tests habe, aber weit davon entfernt bin, ein Guru zu sein, wäre ich sehr daran interessiert, Ihre Gedanken zu diesen Themen zu lesen.
Neben den oben genannten allgemeinen Fragen habe ich einige spezifischere technische Fragen:
Frage 1. Ist es sinnvoll, eine private Methode m einer Klasse A direkt zu testen und sogar öffentlich zu machen, um sie zu testen? Oder sollte ich annehmen, dass m indirekt durch Unit-Tests getestet wird, die andere öffentliche Methoden abdecken, die m aufrufen?
Frage 2. Wenn eine Instanz der Klasse A eine Instanz der Klasse B enthält (zusammengesetzte Aggregation), ist es sinnvoll, B zu verspotten, um A zu testen? Meine erste Idee war, dass ich B nicht verspotten sollte, weil die B-Instanz Teil der A-Instanz ist, aber dann begann ich daran zu zweifeln. Mein Argument gegen das Verspotten von B ist das gleiche wie für 1: B ist privat für A und wird nur für seine Implementierung verwendet, daher scheint das Verspotten von B so, als würde ich private Details von A wie in (1) offenlegen. Aber vielleicht deuten diese Probleme auf einen Konstruktionsfehler hin: Vielleicht sollten wir keine zusammengesetzte Aggregation verwenden, sondern eine einfache Assoziation von A nach B.
Frage 3. Wenn wir uns im obigen Beispiel dazu entschließen, B zu verspotten, wie injizieren wir die B-Instanz in A? Hier sind einige Ideen, die wir hatten:
- Fügen Sie die B-Instanz als Argument in den A-Konstruktor ein, anstatt die B-Instanz im A-Konstruktor zu erstellen.
- Übergeben Sie eine BFactory-Schnittstelle als Argument an den A-Konstruktor und lassen Sie A die Factory verwenden, um seine private B-Instanz zu erstellen.
- Verwenden Sie einen BFactory-Singleton, der für A privat ist. Verwenden Sie eine statische Methode A :: setBFactory (), um den Singleton festzulegen. Wenn A die B-Instanz erstellen möchte, verwendet es den werkseitigen Singleton, wenn dieser festgelegt ist (das Testszenario), und erstellt B direkt, wenn der Singleton nicht festgelegt ist (das Produktionscode-Szenario).
Die ersten beiden Alternativen scheinen mir sauberer zu sein, aber sie erfordern das Ändern der Signatur des A-Konstruktors: Das Ändern einer API, um sie testbarer zu machen, erscheint mir unangenehm. Ist dies eine gängige Praxis?
Der dritte hat den Vorteil, dass die Signatur des Konstruktors nicht geändert werden muss (die Änderung an der API ist weniger invasiv), aber vor dem Start des Tests die statische Methode setBFactory () aufgerufen werden muss, die IMO-fehleranfällig ist ( implizite Abhängigkeit von einem Methodenaufruf, damit die Tests ordnungsgemäß funktionieren). Ich weiß also nicht, welches wir wählen sollen.