Schreiben von Tests für Code, dessen Zweck ich nicht verstehe

59

Ich habe kürzlich ein Black-Box-Refactoring abgeschlossen. Ich kann es nicht einchecken, da ich nicht weiß, wie ich es testen soll.

Auf einer hohen Ebene habe ich eine Klasse, deren Initialisierung das Abrufen von Werten aus einer Klasse B umfasst. Wenn Klasse B "leer" ist, werden einige sinnvolle Standardwerte generiert. Ich habe diesen Teil in eine Methode extrahiert, die Klasse B mit denselben Standardwerten initialisiert.

Ich habe noch nicht herausgefunden, wozu / in welchem ​​Kontext eine Klasse dient oder wie sie verwendet wird. Daher kann ich das Objekt nicht aus einer leeren Klasse B initialisieren und überprüfen, ob es die richtigen Werte hat / das Richtige tut.

Meine beste Idee ist, den Originalcode, den Hardcode in den Ergebnissen öffentlicher Methoden, abhängig von den initialisierten Mitgliedern, auszuführen und den neuen Code dagegen zu testen. Ich kann nicht genau sagen, warum ich mich mit dieser Idee irgendwie unwohl fühle.

Gibt es hier einen besseren Angriff?

JETM
quelle
28
Ich habe das Gefühl, Sie haben am falschen Ende angefangen. Sie sollten den Code zuerst verstehen, dann testen und dann überarbeiten. Warum überarbeiten Sie, ohne zu wissen, wozu der Code dient?
Jacob Raihle
11
@JacobRaihle Es ist ein ziemlich spezialisiertes Programm für Leute mit Abschlüssen in Sachen, die ich noch nie berührt habe. Ich nehme währenddessen den Kontext auf, aber es ist einfach nicht praktisch zu warten, bis ich ein solides Verständnis habe, bevor ich anfange.
JETM
4
Was nicht praktikabel ist, ist das Umschreiben von Dingen und, wenn die Änderungen in der Produktion sind, herauszufinden, warum Sie das nicht sollten. Wenn Sie in der Lage sind, vorher gründlich zu testen , ist dies eine gute Möglichkeit, die Codebasis kennenzulernen. Wenn nicht, müssen Sie dies unbedingt verstehen, bevor Sie sich ändern.
Jacob Raihle
37
Es gibt eine spezielle Art von Test, die als Charakterisierungstest bezeichnet wird , um das tatsächliche Verhalten des Systems zu testen. Nehmen Sie einfach Ihr ursprüngliches System und fügen Sie Tests hinzu, die bestätigen, was auch immer es tatsächlich tut (und nicht unbedingt, was beabsichtigt war!). Sie dienen als Gerüst um Ihr System, das Sie sicher ändern können, da Sie sicherstellen können, dass es sein Verhalten beibehält.
Vincent Savard
3
Kannst du nicht jemanden fragen / überprüfen lassen, der es versteht?
pjc50

Antworten:

122

Du machst das gut!

Das Erstellen automatisierter Regressionstests ist häufig das Beste, was Sie tun können, um eine Komponente überarbeitbar zu machen. Es mag überraschen, aber solche Tests können oft geschrieben werden, ohne zu verstehen, was die Komponente intern tut, solange Sie die Eingabe- und Ausgabe- "Schnittstellen" (in der allgemeinen Bedeutung dieses Wortes) verstehen. Wir haben dies in der Vergangenheit mehrmals für ausgereifte Legacy-Anwendungen durchgeführt, nicht nur für Klassen, und es hat uns oft geholfen, zu vermeiden, dass Dinge beschädigt werden, die wir nicht vollständig verstanden haben.

Sie sollten jedoch über genügend Testdaten verfügen und sicher sein, dass Sie genau wissen, was die Software aus der Sicht eines Benutzers dieser Komponente tut, da Sie sonst das Risiko eingehen, wichtige Testfälle auszulassen.

Es ist meiner Meinung nach eine gute Idee, Ihre automatisierten Tests vor dem Refactoring und nicht danach zu implementieren , damit Sie das Refactoring in kleinen Schritten durchführen und jeden Schritt überprüfen können. Das Refactoring selbst sollte den Code besser lesbar machen, damit Sie die Interna Stück für Stück besser verstehen. So ist die Reihenfolge Schritte in diesem Prozess

  1. den Code "von außen" verstehen lernen,
  2. Schreibe Regressionstests,
  3. refactor, was zu einem besseren Verständnis der Interna des Codes führt
Doc Brown
quelle
21
Perfekte Antwort, auch genau wie im Buch "Arbeiten mit Legacy-Code"
Altoyr
Ich musste so etwas einmal machen. Sammeln Sie typische Ausgabedaten aus der Anwendung, bevor ich sie ändere, und überprüfen Sie dann meine neue Version der Anwendung, indem Sie dieselben Testdaten ausführen. Vor 30 Jahren ... Fortran ... war es eine Art Bildverarbeitungs- / Mapping-Sache, also konnte ich nicht wirklich wissen, was die Ausgabe sein sollte, indem ich sie ansah oder Testfälle schrieb. Und ich habe es auf einem Tektronix-Vektor-Display (persistent) gemacht. Regierungsarbeit ... 2 Teletypen hämmern hinter mir her.
4
Man könnte hinzufügen, dass Sie Ihre Tests für den alten Code auch nachträglich schreiben können. Anschließend können Sie sie in Ihrer überarbeiteten Version testen. Wenn dies nicht funktioniert, durchsuchen Sie den Festschreibungsverlauf in einer Teilung, um den Punkt zu ermitteln, an dem der Fehler auftritt.
CodeMonkey
2
Ich würde vorschlagen, noch etwas zu tun. Sammeln Sie während der Erfassung der Testdaten nach Möglichkeit Statistiken zur Codeabdeckung. Sie wissen, wie gut Ihre Testdaten den fraglichen Code beschreiben.
Liori
2
@nocomprende, Es ist lustig, dass ich letzte Woche genau das mit einem alten wissenschaftlichen fortran 77-Code gemacht habe. Fügen Sie das Drucken von ASCII-Daten zu einer Datei hinzu, richten Sie Testverzeichnisse mit den Eingaben und der erwarteten Ausgabe ein, und mein Testfall war nur ein Unterschied zwischen den beiden Ausgabesätzen. Wenn sie nicht Zeichen für Zeichen übereinstimmen, habe ich etwas kaputt gemacht. Wenn der Code hauptsächlich aus zwei Subroutinen besteht, die jeweils 2-3 k LoC umfassen, müssen Sie irgendwo beginnen.
Godric Seer
1

Ein wichtiger Grund für das Schreiben von Komponententests ist, dass sie die Komponenten-API irgendwie dokumentieren. Hier ist es wirklich ein Problem, den Zweck des zu testenden Codes nicht zu verstehen. Die Codeabdeckung ist ein weiteres wichtiges Ziel, das schwer zu erreichen ist, ohne zu wissen, welche Ausführungszweige existieren und wie sie ausgelöst werden.

Wenn es jedoch möglich ist, den Status sauber zurückzusetzen (oder das neue Testobjekt jedes Mal zu erstellen), kann man einen "Papierkorb-in-Papierkorb-out" -Test schreiben, der dem System nur meist zufällige Eingaben zuführt und die Ausgabe beobachtet.

Solche Tests sind schwierig aufrechtzuerhalten, da es schwierig sein kann, zu sagen, warum und wie schwerwiegend sie sind, wenn sie fehlschlagen. Die Abdeckung kann fraglich sein. Sie sind jedoch immer noch viel besser als nichts. Wenn ein solcher Test fehlschlägt, kann der Entwickler die letzten Änderungen mit größerer Aufmerksamkeit überarbeiten und den Fehler hoffentlich dort finden.

h22
quelle
1
Jede Art von Information ist besser als blind zu fliegen. Früher habe ich Fehler in Serverprogrammen ausfindig gemacht, die in der Produktion waren, indem ich den Debugger in einer Crash-Dump-Datei (Unix) aufgerufen und nach dem Stack-Trace gefragt habe. Es gab mir den Namen der Funktion, in der der Fehler auftrat. Selbst ohne andere Kenntnisse (ich wusste nicht, wie man diesen Debugger verwendet) half es in einer ansonsten mysteriösen und nicht reproduzierbaren Situation.