Wie führen Sie Unit-Tests in eine große Legacy-Codebasis (C / C ++) ein?

74

Wir haben eine große plattformübergreifende Anwendung, die in C geschrieben ist (mit einer kleinen, aber wachsenden Menge an C ++). Sie hat sich im Laufe der Jahre mit vielen Funktionen weiterentwickelt, die Sie von einer großen C / C ++ - Anwendung erwarten würden:

  • #ifdef Hölle
  • Große Dateien, die es schwierig machen, testbaren Code zu isolieren
  • Funktionen, die zu komplex sind, um leicht getestet werden zu können

Da dieser Code für eingebettete Geräte bestimmt ist, ist es sehr aufwändig, ihn auf dem eigentlichen Ziel auszuführen. Daher möchten wir mehr von unserer Entwicklung und unseren Tests in schnellen Zyklen auf einem lokalen System durchführen. Wir möchten jedoch die klassische Strategie "Kopieren / Einfügen in eine C-Datei auf Ihrem System, Beheben von Fehlern, Kopieren / Einfügen zurück" vermeiden. Wenn Entwickler sich die Mühe machen, dies zu tun, möchten wir in der Lage sein, dieselben Tests später neu zu erstellen und automatisiert auszuführen.

Hier ist unser Problem: Um den Code modularer zu gestalten, muss er testbarer sein. Um jedoch automatisierte Komponententests einzuführen, müssen diese modularer sein.

Ein Problem ist, dass wir, da unsere Dateien so groß sind, möglicherweise eine Funktion in einer Datei haben, die eine Funktion in derselben Datei aufruft , die wir für einen guten Komponententest stubben müssen. Es scheint, dass dies weniger problematisch wäre, da unser Code modularer wird, aber das ist noch weit entfernt.

Eine Sache, über die wir nachgedacht haben, war das Markieren des Quellcodes "bekanntermaßen testbar" mit Kommentaren. Dann könnten wir ein Skript-Scan-Quelldateien für testbaren Code schreiben, es in einer separaten Datei kompilieren und es mit den Unit-Tests verknüpfen. Wir könnten die Komponententests langsam einführen, wenn wir Fehler beheben und mehr Funktionen hinzufügen.

Es besteht jedoch die Sorge, dass die Aufrechterhaltung dieses Schemas (zusammen mit allen erforderlichen Stub-Funktionen) zu mühsam wird und die Entwickler die Wartung der Komponententests einstellen. Ein anderer Ansatz besteht darin, ein Tool zu verwenden, das automatisch Stubs für den gesamten Code generiert und die Datei damit verknüpft. (Das einzige Tool, das wir gefunden haben, um dies zu tun, ist ein teures kommerzielles Produkt.) Dieser Ansatz scheint jedoch zu erfordern, dass unser gesamter Code modularer ist, bevor wir überhaupt beginnen können, da nur die externen Aufrufe gelöscht werden können.

Persönlich möchte ich Entwickler lieber über ihre externen Abhängigkeiten nachdenken lassen und intelligent ihre eigenen Stubs schreiben. Dies könnte jedoch überwältigend sein, um alle Abhängigkeiten für eine schrecklich überwachsene Datei mit 10.000 Zeilen zu beseitigen. Es mag schwierig sein, Entwickler davon zu überzeugen, dass sie Stubs für alle ihre externen Abhängigkeiten verwalten müssen, aber ist das der richtige Weg, dies zu tun? (Ein weiteres Argument, das ich gehört habe, ist, dass der Betreuer eines Subsystems die Stubs für sein Subsystem verwalten sollte. Aber ich frage mich, ob das "Erzwingen" von Entwicklern, ihre eigenen Stubs zu schreiben, zu besseren Unit-Tests führen würde?)

Das #ifdefsfügt dem Problem natürlich eine weitere ganze Dimension hinzu.

Wir haben uns mehrere C / C ++ - basierte Unit-Test-Frameworks angesehen, und es gibt viele Optionen, die gut aussehen. Wir haben jedoch nichts gefunden, was den Übergang von "Haarball des Codes ohne Unit-Tests" zu "Unit-testbarem Code" erleichtern könnte.

Hier sind meine Fragen an alle anderen, die dies durchgemacht haben:

  • Was ist ein guter Ausgangspunkt? Gehen wir in die richtige Richtung oder fehlt uns etwas Offensichtliches?
  • Welche Tools könnten hilfreich sein, um den Übergang zu erleichtern? (vorzugsweise Free / Open Source, da unser Budget derzeit ungefähr "Null" ist)

Beachten Sie, dass unsere Build-Umgebung auf Linux / UNIX basiert, sodass wir keine Nur-Windows-Tools verwenden können.

Mike
quelle
5
@ S.Lott Entschuldigung, aber wie ist eine Frage zum Unit-Test ein Betrüger einer Frage zu TDD?
Rob Wells
7
@ S.Lott: Unit Testing ist kein TDD. TDD beinhaltet Unit-Tests, aber Unit-Tests können auch nach dem Schreiben des Codes nachgerüstet werden, worum es in dieser Frage geht. Was den zweiten betrifft, bin ich ebenfalls anderer Meinung. Dies ist spezifisch für C / C ++, Sprachen, die ihre eigenen Herausforderungen beim Testen von Einheiten darstellen.
Jalf
4
Diese Frage passt so gut zu meiner Situation, dass ich sogar überprüft habe, ob ich sie geschrieben habe!
Markus Schnell
1
Verwandte SO-Frage: stackoverflow.com/questions/91683/… vom 18. September 2008.
Linuxbuild
1
@ Mike Ich sehe, dass Sie eine Antwort nicht ausgenommen haben und ich beginne, mich einem ähnlichen Problem zu stellen. Vielleicht könnten Sie Ihre Erfahrungen mit diesem Problem teilen und herausfinden, welche Lösungen in den letzten 5 Jahren für Sie am besten funktioniert haben.
Robbmj

Antworten:

49

Wir haben nichts gefunden, was den Übergang von "Haarball des Codes ohne Unit-Tests" zu "Unit-testbarem Code" erleichtern könnte.

Wie traurig - keine wundersame Lösung - nur viel harte Arbeit, um Jahre akkumulierter technischer Schulden zu korrigieren .

Es gibt keinen einfachen Übergang. Sie haben ein großes, komplexes und ernstes Problem.

Sie können es nur in winzigen Schritten lösen. Jeder winzige Schritt beinhaltet Folgendes.

  1. Wählen Sie einen diskreten Code, der unbedingt erforderlich ist. (Knabbern Sie nicht an den Rändern des Mülls.) Wählen Sie eine Komponente aus, die wichtig ist und - irgendwie - aus dem Rest herausgeschnitten werden kann. Während eine einzelne Funktion ideal ist, kann es sich um einen verworrenen Funktionscluster oder eine ganze Funktionsdatei handeln. Es ist in Ordnung, mit etwas zu beginnen, das für Ihre testbaren Komponenten nicht perfekt ist.

  2. Finde heraus, was es tun soll. Finde heraus, was die Schnittstelle sein soll. Dazu müssen Sie möglicherweise ein erstes Refactoring durchführen, um Ihr Zielstück tatsächlich diskret zu machen.

  3. Schreiben Sie einen "Gesamt" -Integrationstest, der Ihren diskreten Code vorerst mehr oder weniger so testet, wie er gefunden wurde. Lassen Sie dies bestehen, bevor Sie versuchen, etwas Wesentliches zu ändern.

  4. Refaktorieren Sie den Code in ordentliche, testbare Einheiten, die sinnvoller sind als Ihr aktueller Haarball. Sie müssen (vorerst) eine gewisse Abwärtskompatibilität mit Ihrem Gesamtintegrationstest aufrechterhalten.

  5. Schreiben Sie Komponententests für die neuen Einheiten.

  6. Wenn alles vorbei ist, deaktivieren Sie die alte API und beheben Sie, was durch die Änderung beschädigt wird. Überarbeiten Sie gegebenenfalls den ursprünglichen Integrationstest. Es testet die alte API. Sie möchten die neue API testen.

Iterieren.

S.Lott
quelle
Diese Antwort klingt großartig. Es wird jedoch davon ausgegangen, dass wir Zeit im Zeitplan haben, um dies zu tun. ;) Wir tun dies möglicherweise im Grunde genommen, folgen aber nicht genau Ihrem Rat. Das heißt, wir müssen möglicherweise "an den Rändern am Müll knabbern". Der Müll ist der Code, der Fehler enthält, oder der Code, den wir einführen, oder der Code, den wir berühren müssen, um neuen Code einzuführen. Wir haben wahrscheinlich nicht den Luxus, zunächst eine gigantische Kernfunktion auszuwählen.
mpontillo
4
Wenn Sie nicht mit einer Kernfunktion beginnen, ist das Testen optional. Die Manager entscheiden, dass keine Tests erforderlich sind, und geben diese auf. Wenn Sie mit einem Kern beginnen, ist das Testen unerlässlich.
S.Lott
Ich stimme Ihnen zu und ich wünschte, es wäre so einfach. Leider schaut die Wall Street nur auf das nächste Quartal. Das mittlere Management möchte, dass Tests unerlässlich sind, aber das obere Management möchte so schnell wie möglich mehr Funktionen! Sie wollen Qualität, Umfang und Zeitplan, aber sie sind nicht bereit, einen Umfang oder einen Zeitplan-Treffer zu erzielen. Wir müssen also ein Gleichgewicht finden.
mpontillo
3
@ Mike: Es ist sehr einfach. Der Managementkonflikt wird - unweigerlich - die Anstrengung zum Scheitern verurteilen, es sei denn, Sie beschäftigen sich mit etwas, das wichtig ist. Die Situation ist schlecht, aber nicht komplex. Speichern Sie Ihre E-Mails und kichern Sie wissentlich, wenn der Test für ein Modul abgebrochen oder außer Kraft gesetzt wird.
S.Lott
1
Der Schmerz jedes Entwicklers kommt spät in ein Projekt. :(
Narayana
8

Meine kleine Erfahrung mit Legacy-Code und der Einführung von Tests wäre es, die " Charakterisierungstests ". Sie erstellen Tests mit bekannter Eingabe und erhalten dann die Ausgabe. Diese Tests sind nützlich für Methoden / Klassen, bei denen Sie nicht wissen, was sie wirklich tun, aber Sie wissen, dass sie funktionieren.

Es gibt jedoch manchmal Fälle, in denen es fast unmöglich ist, Komponententests zu erstellen (sogar Charakterisierungstests). In diesem Fall greife ich das Problem durch Abnahmetests an ( greife in diesem Fall Fitnesse ).

Sie erstellen die gesamte Klasse von Klassen, die zum Testen einer Funktion erforderlich sind, und überprüfen sie auf Fitness. Es ähnelt "Charakterisierungstests", ist aber eine Stufe höher.

Edison Gustavo Muenz
quelle
7

Wie George sagte, ist die effektive Arbeit mit Legacy Code die Bibel für solche Dinge.

Die andere Möglichkeit für andere in Ihrem Team besteht jedoch darin, sich persönlich davon zu überzeugen, dass die Tests weiterhin funktionieren.

Um dies zu erreichen, benötigen Sie ein Testframework, mit dem Sie so einfach wie möglich arbeiten können. Planen Sie für andere Entwickler, dass Sie Ihre Tests als Beispiele verwenden, um ihre eigenen zu schreiben. Wenn sie keine Erfahrung mit Unit-Tests haben und nicht erwarten, dass sie Zeit mit dem Erlernen eines Frameworks verbringen, werden sie das Schreiben von Unit-Tests wahrscheinlich als Verlangsamung ihrer Entwicklung ansehen. Wenn sie das Framework nicht kennen, ist dies eine Ausrede, um die Tests zu überspringen.

Verbringen Sie einige Zeit mit der kontinuierlichen Integration mithilfe von Tempomat, Luntbuild, CDash usw. Wenn Ihr Code jede Nacht automatisch kompiliert wird und Tests ausgeführt werden, werden Entwickler die Vorteile erkennen, wenn Unit-Tests vor der Qa Fehler erkennen.

Eine Sache, die gefördert werden sollte, ist der gemeinsame Besitz von Code. Wenn ein Entwickler seinen Code ändert und den Test einer anderen Person bricht, sollte er nicht erwarten, dass diese Person seinen Test repariert. Er sollte untersuchen, warum der Test nicht funktioniert, und ihn selbst reparieren. Nach meiner Erfahrung ist dies eines der am schwierigsten zu erreichenden Dinge.

Die meisten Entwickler schreiben eine Art Unit-Test, manchmal ein kleines Stück Wegwerfcode, den sie nicht einchecken oder in den Build integrieren. Machen Sie die Integration dieser in den Build einfach und Entwickler werden anfangen, sich einzukaufen.

Mein Ansatz ist es, Tests für neuen hinzuzufügen, und wenn der Code geändert wird, können Sie manchmal nicht so viele oder so detaillierte Tests hinzufügen, wie Sie möchten, ohne zu viel vorhandenen Code zu entkoppeln.

Der einzige Ort, an dem ich auf Unit-Tests bestehe, ist plattformspezifischer Code. Wenn #ifdefs durch plattformspezifische Funktionen / Klassen höherer Ebenen ersetzt werden, müssen diese auf allen Plattformen mit denselben Tests getestet werden. Dies spart viel Zeit beim Hinzufügen neuer Plattformen.

Wir verwenden boost :: test, um unseren Test zu strukturieren. Die einfachen selbstregistrierenden Funktionen erleichtern das Schreiben von Tests.

Diese sind in CTest (Teil von CMake) eingeschlossen. Dadurch wird eine Gruppe von ausführbaren Unit-Test-Dateien gleichzeitig ausgeführt und ein einfacher Bericht erstellt.

Unser nächtlicher Build wird mit Ant und Luntbuild automatisiert (Ant Glues C ++, .net und Java Builds).

Bald hoffe ich, dem Build automatisierte Bereitstellungs- und Funktionstests hinzuzufügen.

iain
quelle
5

Genau das tun wir gerade. Vor drei Jahren trat ich dem Entwicklungsteam bei einem Projekt bei, das keine Komponententests, fast keine Codeüberprüfungen und einen ziemlich ad-hoc-Erstellungsprozess aufwies.

Die Codebasis besteht aus einer Reihe von COM-Komponenten (ATL / MFC), einer plattformübergreifenden C ++ - Oracle-Datenkassette und einigen Java-Komponenten, die alle eine plattformübergreifende C ++ - Kernbibliothek verwenden. Ein Teil des Codes ist fast ein Jahrzehnt alt.

Der erste Schritt war das Hinzufügen einiger Komponententests. Leider ist das Verhalten sehr datengesteuert, so dass einige anfängliche Anstrengungen unternommen wurden, um ein Unit-Test-Framework (ursprünglich CppUnit, jetzt erweitert auf andere Module mit JUnit und NUnit) zu generieren, das Testdaten aus einer Datenbank verwendet. Die meisten anfänglichen Tests waren Funktionstests, bei denen die äußersten Schichten trainiert wurden, und keine wirklichen Einheitentests. Sie müssen wahrscheinlich einige Anstrengungen unternehmen (die Sie möglicherweise budgetieren müssen), um ein Testkabel zu implementieren.

Ich finde es sehr hilfreich, wenn Sie die Kosten für das Hinzufügen von Unit-Tests so gering wie möglich halten. Das Test-Framework machte es relativ einfach, Tests hinzuzufügen, wenn Fehler in vorhandenen Funktionen behoben wurden. Neuer Code kann ordnungsgemäße Komponententests enthalten. Wenn Sie neue Codebereiche umgestalten und implementieren, können Sie geeignete Komponententests hinzufügen, mit denen viel kleinere Codebereiche getestet werden.

Im letzten Jahr haben wir die kontinuierliche Integration mit CruiseControl hinzugefügt und unseren Erstellungsprozess automatisiert. Dies ist ein viel größerer Anreiz, die Tests auf dem neuesten Stand zu halten und zu bestehen, was in den frühen Tagen ein großes Problem war. Daher würde ich empfehlen, dass Sie regelmäßige (mindestens nächtliche) Unit-Testläufe als Teil Ihres Entwicklungsprozesses einbeziehen.

Wir haben uns kürzlich darauf konzentriert, unseren Codeüberprüfungsprozess zu verbessern, der ziemlich selten und ineffektiv war. Die Absicht ist, es viel billiger zu machen, eine Codeüberprüfung zu initiieren und durchzuführen, damit Entwickler dazu ermutigt werden, sie häufiger durchzuführen. Auch im Rahmen unserer Prozessverbesserung versuche ich, Zeit für Codeüberprüfungen und Komponententests, die in die Projektplanung einbezogen werden, auf einer viel niedrigeren Ebene zu erhalten, um sicherzustellen, dass einzelne Entwickler mehr über sie nachdenken müssen, während es zuvor nur einen festen Anteil gab Zeit, die ihnen gewidmet war, war viel einfacher, sich im Zeitplan zu verlieren.

Robert Tuck
quelle
4

Ich habe sowohl an Green Field-Projekten mit vollständig einheitlich getesteten Codebasen als auch an großen C ++ - Anwendungen gearbeitet, die über viele Jahre gewachsen sind, und mit vielen verschiedenen Entwicklern.

Ehrlich gesagt würde ich mich nicht darum kümmern, eine Legacy-Codebasis in den Zustand zu versetzen, in dem Unit-Tests und Test First Development einen großen Mehrwert bieten können.

Sobald eine Legacy-Codebasis eine bestimmte Größe und Komplexität erreicht hat, wird sie zu einer Aufgabe, die einer vollständigen Neufassung entspricht.

Das Hauptproblem besteht darin, dass Sie, sobald Sie mit dem Refactoring für die Testbarkeit beginnen, Fehler einführen. Und erst wenn Sie eine hohe Testabdeckung erhalten, können Sie davon ausgehen, dass all diese neuen Fehler gefunden und behoben werden.

Das bedeutet, dass Sie entweder sehr langsam und vorsichtig vorgehen und erst in Jahren die Vorteile einer gut einheitlich getesteten Codebasis erhalten. (Wahrscheinlich nie seit Fusionen usw.) In der Zwischenzeit führen Sie wahrscheinlich einige neue Fehler ein, die für den Endbenutzer der Software keinen offensichtlichen Wert haben.

Oder Sie gehen schnell, haben aber eine instabile Codebasis, bis Sie eine hohe Testabdeckung Ihres gesamten Codes erreicht haben. (Sie haben also zwei Niederlassungen, eine in Produktion, eine für die Unit-getestete Version.)

Natürlich ist dies alles eine Frage des Maßstabs für einige Projekte. Ein Umschreiben kann nur einige Wochen dauern und kann sich auf jeden Fall lohnen.

Jeroen Dirks
quelle
Sie können den Code nur ein wenig umgestalten, um ihn testbar zu machen. Dann schreiben Sie Tests und führen schließlich den vollständigen Refactor durch. Das Aufgeben der alten Codebasis ist nicht einfach. Meistens enthält der Code versteckte Annahmen oder "offensichtliche" (für Benutzer) Logik, die wesentlich sind. Von vorne anfangen ist ein sicherer Weg, sie fallen zu lassen.
Hubert Kario
3

Ein zu berücksichtigender Ansatz besteht darin, zunächst ein systemweites Simulationsframework einzurichten, mit dem Sie Integrationstests entwickeln können. Das Beginnen mit Integrationstests mag kontraintuitiv erscheinen, aber die Probleme bei der Durchführung echter Unit-Tests in der von Ihnen beschriebenen Umgebung sind gewaltig. Wahrscheinlich mehr als nur die Simulation der gesamten Laufzeit in Software ...

Dieser Ansatz würde einfach Ihre aufgelisteten Probleme umgehen - obwohl er Ihnen viele verschiedene Probleme geben würde. In der Praxis habe ich jedoch festgestellt, dass Sie mit einem robusten Integrationstest-Framework Tests entwickeln können, die Funktionen auf Einheitenebene ausüben, allerdings ohne Einheitenisolation.

PS: Erwägen Sie, ein befehlsgesteuertes Simulationsframework zu schreiben, das möglicherweise auf Python oder Tcl basiert. Auf diese Weise können Sie ganz einfach Skripttests durchführen ...

Jeff Kotula
quelle
Sehr guter Rat! Mit guten Integrationstests kann er beginnen, den Code so umzugestalten, dass er modularer und einheitlicher testbar ist. Ohne irgendwelche Tests wäre es viel zu riskant, mit dem Refactoring in mehr Unit-testbaren Code zu beginnen.
JacquesB
Danke für die Antwort. Eine Gesamtsystemsimulation wäre großartig. Dies ist eine Möglichkeit, aber es ist eine enorme Menge an Arbeit. Einige Module sind bereits so weit voneinander getrennt, dass die Entwickler in ihrer eigenen Simulationsumgebung ausgeführt werden können. Im Moment würde ich jedoch sagen, dass dies die Ausnahme und nicht die Regel ist.
mpontillo
3

Tag auch,

Ich würde zunächst einen Blick auf offensichtliche Punkte werfen, z. B. die Verwendung von Dez. in Header-Dateien für einen.

Schauen Sie sich dann an, wie der Code angelegt wurde. Ist es logisch? Fangen Sie vielleicht an, große Dateien in kleinere zu zerlegen.

Vielleicht holen Sie sich ein Exemplar von Jon Lakos 'ausgezeichnetem Buch "Large-Scale C ++ Software Design" ( bereinigter Amazon-Link ), um einige Ideen zu erhalten, wie es aufgebaut sein sollte.

Sobald Sie anfangen, ein bisschen mehr Vertrauen in die Codebasis selbst zu haben, dh das Code-Layout wie im Dateilayout, und einige der schlechten Gerüche beseitigt haben, z. B. die Verwendung von Dez. in Header-Dateien, können Sie einige Funktionen auswählen, die Sie können Verwenden Sie diese Option, um mit dem Schreiben Ihrer Komponententests zu beginnen.

Wählen Sie eine gute Plattform, ich mag CUnit und CPPUnit, und gehen Sie von dort aus.

Es wird jedoch eine lange, langsame Reise.

HTH

Prost,

Rob Wells
quelle
2

Es ist viel einfacher, es zuerst modularer zu gestalten. Mit vielen Abhängigkeiten kann man etwas nicht wirklich vereinheitlichen. Wann umgestaltet werden muss, ist eine schwierige Berechnung. Sie müssen wirklich die Kosten und Risiken gegen die Vorteile abwägen. Wird dieser Code ausgiebig wiederverwendet? Oder wird sich dieser Code wirklich nicht ändern? Wenn Sie vorhaben, es weiterhin zu nutzen, möchten Sie es wahrscheinlich umgestalten.

Klingt so, als ob Sie umgestalten möchten. Sie müssen zunächst die einfachsten Dienstprogramme ausbrechen und darauf aufbauen. Sie haben Ihr C-Modul, das eine Unmenge von Dingen erledigt. Vielleicht gibt es dort einen Code, der Zeichenfolgen immer auf eine bestimmte Weise formatiert. Möglicherweise kann dies als eigenständiges Dienstprogrammmodul herausgestellt werden. Sie haben Ihr neues Zeichenfolgenformatierungsmodul und den Code lesbarer gemacht. Es ist schon eine Verbesserung. Sie behaupten, dass Sie sich in einer Situation befinden. Das bist du wirklich nicht. Durch einfaches Verschieben haben Sie den Code lesbarer und wartbarer gemacht.

Jetzt können Sie eine Unittest für dieses ausgebrochene Modul erstellen. Sie können das auf verschiedene Arten tun. Sie können eine separate App erstellen, die nur Ihren Code enthält und eine Reihe von Fällen in einer Hauptroutine auf Ihrem PC ausführt, oder eine statische Funktion namens "UnitTest" definieren, die alle Testfälle ausführt und "1" zurückgibt, wenn sie erfolgreich sind. Dies könnte auf dem Ziel ausgeführt werden.

Vielleicht können Sie mit diesem Ansatz nicht 100% gehen, aber es ist ein Anfang, und es kann dazu führen, dass Sie andere Dinge sehen, die leicht in testbare Dienstprogramme aufgeteilt werden können.

Doug T.
quelle
2

Ich denke, im Grunde haben Sie zwei verschiedene Probleme:

  1. Große Codebasis zum Refactor
  2. Arbeite mit einem Team

Modularisierung, Refactoring, Einfügen von Unit-Tests und dergleichen ist eine schwierige Aufgabe, und ich bezweifle, dass jedes Tool größere Teile dieser Arbeit übernehmen könnte. Es ist eine seltene Fähigkeit. Einige Programmierer können das sehr gut. Die meisten hassen es.

Eine solche Aufgabe mit einem Team zu erledigen, ist mühsam. Ich bezweifle stark, dass das "Erzwingen" von Entwicklern jemals funktionieren wird. Iains Gedanken sind sehr gut, aber ich würde in Betracht ziehen, einen oder zwei Programmierer zu finden, die in der Lage sind und die Quellen "bereinigen" wollen: Refactor, Modualrize, Unit Tests einführen usw. Lassen Sie diese Leute den Job machen und die anderen stellen neue vor Bugs, Aehm-Funktionen. Nur Menschen, die diese Art von Arbeit mögen , werden mit diesem Job Erfolg haben.

RED SOFT ADAIR
quelle
Sehr wichtiger Punkt, um die "richtigen" Leute für die Arbeit zu gewinnen. Dies könnte den Unterschied in realen Projekten ausmachen. Dies erleichtert auch die Berücksichtigung der Kosten (n zusätzliche Personen anstelle von n Personen, die einen nicht quantifizierbaren Teil ihrer kostbaren Zeit zur Verfügung stellen).
A. Robert
1

Machen Sie die Verwendung von Tests einfach.

Ich würde damit beginnen, die "Läufe automatisch" einzurichten. Wenn Sie möchten, dass Entwickler (einschließlich Sie selbst) Tests schreiben, machen Sie es einfach, sie auszuführen und die Ergebnisse anzuzeigen.

Das Schreiben eines Tests mit drei Zeilen, das Ausführen mit dem neuesten Build und das Anzeigen der Ergebnisse sollten nur einen Klick entfernt sein und den Entwickler nicht an die Kaffeemaschine senden.

Dies bedeutet, dass Sie einen neuesten Build benötigen. Möglicherweise müssen Sie die Richtlinien für die Arbeit mit Code usw. ändern. Ich weiß, dass ein solcher Prozess eine PITA mit eingebetteten Geräten sein kann, und ich kann dazu keine Ratschläge geben. Aber ich weiß, dass niemand sie schreiben wird, wenn es schwierig ist, die Tests auszuführen.

Testen Sie, was getestet werden kann

Ich weiß, dass ich hier gegen die gängige Unit-Test-Philosophie verstoße, aber das ist, was ich tue: Schreibe Tests für die Dinge, die einfach zu testen sind. Ich mache mir keine Gedanken über das Verspotten, ich überarbeite es nicht, um es testbar zu machen, und wenn es sich um eine Benutzeroberfläche handelt, habe ich keinen Komponententest. Aber immer mehr meiner Bibliotheksroutinen haben eine.

Ich bin ziemlich erstaunt, welche einfachen Tests dazu neigen, zu finden. Das Pflücken der niedrig hängenden Früchte ist keineswegs nutzlos.

Anders ausgedrückt: Sie würden nicht vorhaben, dieses riesige Haarball-Chaos aufrechtzuerhalten, wenn es kein erfolgreiches Produkt wäre. Ihre derzeitige Qualitätskontrolle ist kein Totalausfall, der ersetzt werden muss. Verwenden Sie Unit-Tests lieber dort, wo sie einfach durchzuführen sind.

(Sie müssen es jedoch erledigen. Lassen Sie sich nicht darauf ein, "alles zu reparieren", was Ihren Erstellungsprozess betrifft.)

Lehren Sie, wie Sie Ihre Codebasis verbessern können

Jede Codebasis mit dieser Historie schreit nach Verbesserungen, das ist sicher. Sie werden jedoch nie alles umgestalten.

Wenn man sich zwei Codeteile mit derselben Funktionalität ansieht, können die meisten Menschen zustimmen, welches unter einem bestimmten Aspekt (Leistung, Lesbarkeit, Wartbarkeit, Testbarkeit, ...) "besser" ist. Die harten Teile sind drei:

  • wie man die verschiedenen Aspekte ausbalanciert
  • wie man zustimmt, dass dieser Code gut genug ist
  • wie man schlechten Code in ausreichend guten Code verwandelt, ohne etwas zu beschädigen.

Der erste Punkt ist wahrscheinlich der schwierigste und ebenso eine soziale wie eine technische Frage. Aber die anderen Punkte können gelernt werden. Ich kenne keine formalen Kurse, die diesen Ansatz verfolgen, aber vielleicht können Sie etwas intern organisieren: alles, von zwei Jungs, die sich zusammenfinden, bis zu "Workshops", in denen Sie ein böses Stück Code nehmen und darüber diskutieren, wie Sie es verbessern können.


Peterchen
quelle
1

Das alles hat einen philosophischen Aspekt.

Möchten Sie wirklich getesteten, voll funktionsfähigen und aufgeräumten Code? Ist es IHR Ziel? Profitieren SIE überhaupt davon?

ja, das klingt zunächst total dumm. Aber ehrlich gesagt, wenn Sie nicht der eigentliche Eigentümer des Systems und nicht nur ein Mitarbeiter sind, bedeuten Fehler einfach mehr Arbeit, mehr Arbeit bedeutet mehr Geld. Sie können total glücklich sein, wenn Sie an einem Haarball arbeiten.

Ich vermute hier nur, aber das Risiko, das Sie eingehen, wenn Sie sich diesem großen Kampf stellen, ist wahrscheinlich viel höher als die mögliche Rückzahlung, die Sie erhalten, wenn Sie den Code aufräumen. Wenn Ihnen die sozialen Fähigkeiten fehlen, um dies durchzusetzen, werden Sie nur als Unruhestifter angesehen. Ich habe diese Leute gesehen und ich war auch einer. Aber natürlich ist es ziemlich cool, wenn Sie das durchziehen. Ich wäre beeindruckt.

Aber wenn Sie das Gefühl haben, jetzt zusätzliche Stunden zu verbringen, um ein unordentliches System am Laufen zu halten, glauben Sie wirklich, dass sich dies ändern wird, sobald der Code aufgeräumt und schön wird? Nein. Sobald der Code schön und ordentlich ist, haben die Leute all diese Freizeit, um ihn zum ersten verfügbaren Termin wieder vollständig zu zerstören.

Am Ende ist es das Management, das den Arbeitsplatz schön macht, nicht der Code.

Per Viberg
quelle
0

Ich bin mir nicht sicher, ob es tatsächlich ist oder nicht, aber ich habe hier einen kleinen Rat. Soweit ich weiß, stellen Sie methodische Fragen zur inkrementellen nicht-invasiven Integration von Unit-Tests in riesigen Legacy-Code, wobei viele Stakeholder ihren Sumpf schützen.

Normalerweise besteht der erste Schritt darin, Ihren Testcode unabhängig von allen anderen Codes zu erstellen. Selbst dieser Schritt in langlebigem Legacy-Code ist sehr komplex. Ich schlage vor, Ihren Testcode als dynamische gemeinsam genutzte Bibliothek mit Laufzeitverknüpfung zu erstellen. Auf diese Weise können Sie nur einen kleinen Teil des Codes überarbeiten, der nicht getestet wird, und nicht die gesamte 20K-Datei. Sie können also damit beginnen, Funktion für Funktion abzudecken, ohne alle Verknüpfungsprobleme zu berühren / zu beheben

Kamil Garifullin
quelle