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.
quelle
Antworten:
Ich denke, öffentliche Methoden zu testen ist die meiste Zeit genug.
Wenn Ihre privaten Methoden sehr komplex sind, sollten Sie sie als öffentliche Methoden in eine andere Klasse einfügen und als private Aufrufe dieser Methoden in Ihrer ursprünglichen Klasse verwenden. Auf diese Weise können Sie sicherstellen, dass beide Methoden in Ihrer Original- und Dienstprogrammklasse ordnungsgemäß funktionieren.
Bei Designentscheidungen ist es wichtig, sich stark auf private Methoden zu verlassen.
quelle
Zu Frage 1: Das kommt darauf an. In der Regel beginnen Sie mit Komponententests für öffentliche Methoden. Manchmal stoßen Sie auf eine Methode m, die Sie für A privat halten möchten, aber Sie denken, dass es sinnvoll ist, m auch isoliert zu testen. Wenn dies der Fall ist, sollten Sie entweder m öffentlich machen oder die Testklasse zu
TestA
einer Freundesklasse machenA
. Beachten Sie jedoch, dass das Hinzufügen eines Komponententests zum Testen von m es etwas schwieriger macht, die Signatur oder das Verhalten von m anschließend zu ändern. Wenn Sie dieses "Implementierungsdetail" von A beibehalten möchten, ist es möglicherweise besser, keinen direkten Komponententest hinzuzufügen.Frage 2: (C ++ - integrierte) zusammengesetzte Aggregation funktioniert nicht gut, wenn es darum geht, eine Instanz zu verspotten. Da die Konstruktion des B implizit im Konstruktor von A erfolgt, haben Sie keine Chance, die Abhängigkeit von außen zu injizieren. Wenn dies ein Problem ist, hängt es von der Art und Weise ab, wie Sie A testen möchten: Wenn Sie der Meinung sind, dass es sinnvoller ist, A isoliert mit einem Mock von B anstelle von B zu testen, verwenden Sie besser eine einfache Zuordnung. Wenn Sie der Meinung sind, dass Sie alle erforderlichen Komponententests für A schreiben können, ohne B zu verspotten, ist ein Composite wahrscheinlich in Ordnung.
Frage 3: Das Ändern einer API, um die Testbarkeit zu verbessern, ist üblich, solange Sie bisher nicht viel Code haben, der sich auf diese API verlässt. Wenn Sie TDD ausführen, müssen Sie Ihre API danach nicht mehr ändern, um die Testbarkeit zu verbessern. Sie beginnen zunächst mit einer API, die auf Testbarkeit ausgelegt ist. Wenn Sie eine API später ändern möchten, um die Testbarkeit zu verbessern, können Probleme auftreten. Daher würde ich mich für die erste oder zweite der von Ihnen beschriebenen Alternativen entscheiden, solange Sie Ihre API problemlos ändern können, und so etwas wie Ihre dritte Alternative (Anmerkung: Dies funktioniert auch ohne das Singleton-Muster) nur als letzten Ausweg verwenden, wenn ich muss Ändern Sie die API unter keinen Umständen.
Über Ihre Bedenken, dass Sie "es falsch machen" könnten: Jeder große Motor oder jede große Maschine hat Wartungsöffnungen, daher ist es meiner Meinung nach nicht allzu überraschend, dass Software so etwas hinzugefügt werden muss.
quelle
Sie sollten nach Dependency Injection und Inversion of Control suchen. Misko Hevery erklärt viel davon in seinem Blog. DI und IoC sind effektiv ein Entwurfsmuster für die Erstellung von Code, der einfach zu testen und zu verspotten ist.
Frage 1: Nein, machen Sie keine privaten Methoden öffentlich, um sie zu testen. Wenn die Methode ausreichend komplex ist, können Sie eine Collaborator- Klasse erstellen , die nur diese Methode enthält, und sie in Ihre andere Klasse einfügen (an den Konstruktor übergeben). 1
Frage 2: Wer konstruiert in diesem Fall A? Wenn Sie eine Factory / Builder-Klasse haben, die A erstellt, kann es nicht schaden, den Mitarbeiter B an den Konstruktor zu übergeben. Wenn sich A in einem anderen Paket / Namespace befindet als der Code, der es verwendet, können Sie den Konstruktor sogar paketprivat machen und so festlegen, dass die Factory / der Builder die einzige Klasse ist, die ihn erstellen kann.
Frage 3: Ich habe dies in Frage 2 beantwortet. Einige zusätzliche Hinweise:
1 Dies ist die C # / Java-Antwort. C ++ verfügt möglicherweise über zusätzliche Funktionen, die dies erleichtern
Als Antwort auf Ihre Kommentare:
Was ich damit meinte war, dass Ihr Produktionscode von geändert werden würde (bitte verzeihen Sie mein Pseudo-C ++):
zu:
Dann kann Ihr Test sein:
Auf diese Weise müssen Sie die Factory nicht weitergeben, der Code, der A aufruft, muss nicht wissen, wie A erstellt wird, und der Test kann A benutzerdefiniert erstellen, damit das Verhalten getestet werden kann.
In Ihrem anderen Kommentar haben Sie nach verschachtelten Abhängigkeiten gefragt. Zum Beispiel, wenn Ihre Abhängigkeiten waren:
Die erste Frage ist, ob A C und D verwendet. Wenn nicht, warum sind sie in A enthalten? Angenommen, sie werden verwendet, ist es möglicherweise erforderlich, C in Ihrer Fabrik zu übergeben und Ihren Test eine MockC erstellen zu lassen, die eine MockB zurückgibt, sodass Sie alle möglichen Interaktionen testen können.
Wenn dies langsam kompliziert wird, kann dies ein Zeichen dafür sein, dass Ihr Design möglicherweise zu eng miteinander verbunden ist. Wenn Sie die Kupplung lösen und die Kohäsion hoch halten können, ist diese Art von DI einfacher zu implementieren.
quelle