Wie kann die Codeabdeckung drastisch verbessert werden?


21

Ich werde damit beauftragt, eine Legacy-Anwendung unter Unit-Test zu stellen. Zunächst einige Hintergrundinformationen zur Anwendung: Es handelt sich um eine Java-RCP-Codebasis mit 600.000 LOC mit diesen Hauptproblemen

  • massive Code-Vervielfältigung
  • Keine Kapselung, die meisten privaten Daten sind von außen zugänglich, einige der Geschäftsdaten wurden auch als Singletons erstellt, sodass sie nicht nur von außen, sondern auch von überall aus geändert werden können.
  • Keine Abstraktionen (z. B. kein Geschäftsmodell, Geschäftsdaten werden in Object [] und double [] [] gespeichert), also kein OO.

Es gibt eine gute Regressionstestsuite und ein effizientes QA-Team testet und findet Fehler. Ich kenne die Techniken, wie man es aus klassischen Büchern, z. B. Michael Feathers, testen kann, aber das ist zu langsam. Da es ein funktionierendes Regressionstestsystem gibt, habe ich keine Angst, das System aggressiv umzugestalten, damit Komponententests geschrieben werden können.

Wie soll ich anfangen, das Problem anzugreifen, um schnell eine Berichterstattung zu erhalten , damit ich dem Management Fortschritte zeigen kann (und tatsächlich anfange, aus dem Sicherheitsnetz der JUnit-Tests Geld zu verdienen)? Ich möchte keine Tools zum Generieren von Regressionstestsuiten verwenden, z. B. AgitarOne, da diese Tests nicht prüfen, ob etwas korrekt ist.


Warum werden die Regressionstests nicht automatisch erstellt und einzeln überprüft? Muss schneller sein, als sie alle von Hand zu schreiben.
Robert Harvey

Es klingt ein bisschen komisch, etwas zu nennen, das in Java-Legacy geschrieben wurde, aber ich stimme zu, es ist sicherlich Legacy. Sie erwähnen, dass Sie keine Angst davor haben, das System umzugestalten, um das Schreiben von Komponententests zu ermöglichen, aber sollten Sie die Komponententests nicht so auf das System schreiben, wie sie sind, bevor ein Umbau versucht wird? Dann kann Ihr Refactoring die gleichen Komponententests durchlaufen, um sicherzustellen, dass nichts kaputt ist?
dodgy_coder

1
@dodgy_coder Normalerweise stimme ich zu, aber ich hoffe, dass die traditionelle QS, die effizient funktioniert, mir einige Zeit erspart.
Peter Kofler

1
@dodgy_coder Michael C. Feathers, Autor von Effektiv mit Legacy-Code arbeiten, definiert Legacy-Code als "Code ohne Tests". Es dient als nützliche Definition.
StuperUser

Antworten:


10

Ich glaube, es gibt zwei Hauptachsen, entlang derer Code platziert werden kann, wenn es um die Einführung von Komponententests geht: A) Wie prüfbar ist der Code? und B) wie stabil ist es (dh wie dringend braucht es Tests)? Betrachtet man nur die Extreme, ergeben sich 4 Kategorien:

  1. Code, der leicht zu testen und spröde ist
  2. Einfach zu testender und stabiler Code
  3. Code, der schwer zu testen und spröde ist
  4. Code, der schwer zu testen und stabil ist

Kategorie 1 ist der naheliegende Einstieg, bei dem Sie mit relativ wenig Arbeit viel profitieren können. Kategorie 2 ermöglicht es Ihnen, Ihre Abdeckungsstatistik schnell zu verbessern (gut für die Moral) und mehr Erfahrung mit der Codebasis zu sammeln, während Kategorie 3 mehr (oft frustrierende) Arbeit ist, aber auch mehr Nutzen bringt. Was Sie als Erstes tun sollten, hängt davon ab, wie wichtig Moral- und Abdeckungsstatistiken für Sie sind. Kategorie 4 ist wahrscheinlich keine Mühe wert.


Ausgezeichnet. Ich habe eine Idee, wie ich feststellen kann, ob es einfach ist, durch statische Analyse zu überprüfen, z. B. Abhängigkeitszählung / Testability Explorer. Aber wie kann ich feststellen, ob der Code spröde ist? Ich kann Defekte nicht bestimmten Einheiten zuordnen (z. B. Klassen) und natürlich der Nummer 3 (Gottklassen / Singletons). Also vielleicht die Anzahl der Checkins (die Hotspots)?
Peter Kofler

1
@Peter Kofler: Commit-Hotspots sind eine gute Idee, aber die wertvollste Quelle für diese Art von Wissen sind Entwickler, die mit dem Code gearbeitet haben.
Michael Borgwardt

1
@ Peter - wie Michael sagte, die Entwickler, die mit dem Code gearbeitet haben. Jeder, der längere Zeit mit einer großen Codebasis gearbeitet hat, weiß, welche Teile davon riechen. Oder, wenn die ganze Sache riecht, welche Teile davon wirklich stinken .
Carson63000

15

Ich habe viel Erfahrung in der Arbeit mit älteren Systemen (allerdings nicht mit Java), die viel größer sind als diese. Ich hasse es, der Überbringer von schlechten Nachrichten zu sein. Ihr Problem ist die Größe Ihres Problems. Ich vermute, Sie haben es unterschätzt.

Das Hinzufügen von Regressionstests zum Legacy-Code ist ein langsamer und teurer Prozess. Viele Anforderungen sind nicht gut dokumentiert - ein Bugfix hier, ein Patch dort und bevor Sie es wissen, definiert die Software ihr eigenes Verhalten. Wenn keine Tests durchgeführt werden, bedeutet dies, dass nur die Implementierung ausgeführt werden muss. Es sind keine Tests erforderlich, um die im Code implementierten impliziten Anforderungen in Frage zu stellen.

Wenn Sie versuchen, schnell Berichterstattung zu erhalten, werden Sie wahrscheinlich den Job beschleunigen, zur Hälfte backen und scheitern. Die Tests decken das Offensichtliche nur teilweise und die tatsächlichen Probleme nur unzureichend ab. Sie werden überzeugen, dass es sich nicht lohnt, genau die Manager zu überzeugen, die Sie an Unit Testing verkaufen möchten, dass dies nur eine weitere Silberkugel ist, die nicht funktioniert.

Meiner Meinung nach ist der beste Ansatz, Sie auf Tests auszurichten. Verwenden Sie Metriken, Bauchgefühl und Fehlerprotokollberichte, um 1% oder 10% des Codes zu identifizieren, der die meisten Probleme verursacht. Schlagen Sie diese Module hart und ignorieren Sie den Rest. Versuchen Sie nicht zu viel zu tun, weniger ist mehr.

Ein realistisches Ziel ist "Seit wir UT implementiert haben, ist die Defekteinfügung in zu testenden Modulen auf x% derjenigen gesunken, die nicht unter UT sind" (idealerweise ist x eine Zahl <100).


+1, Sie können nichts effektiv testen, ohne einen stärkeren Standard als den Code zu haben.
dan_waterworth

Ich weiß und stimme zu. Der Unterschied besteht darin, dass wir einen Test haben, einen traditionellen Regressionstest durch funktionierende Qualitätssicherung, also gibt es eine Art Sicherheitsnetz. Zweitens bin ich zutiefst für Unit-Tests, es wird also definitiv keine andere Sache sein, die nicht funktioniert hat. Ein guter Punkt, worauf man zuerst abzielt. Vielen Dank.
Peter Kofler

1
und vergessen Sie nicht, dass das bloße Streben nach "Deckung" die Qualität nicht verbessern wird, da Sie in einem Morast fehlerhafter und trivialer Tests stecken bleiben (und Tests für trivialen Code, für den keine expliziten Tests erforderlich sind, aber werden nur hinzugefügt, um die Abdeckung zu erhöhen). Sie werden am Ende Tests erstellen, die dem Coverage-Tool gefallen, nicht weil sie nützlich sind, und möglicherweise den Code selbst ändern, um die Testabdeckung zu erhöhen, ohne Tests zu schreiben (wie das Ausschneiden von Kommentaren und Variablendefinitionen, die einige Coverage-Tools verwenden) wird aufgedeckten Code aufrufen).
5.

2

Ich erinnere mich an das Sprichwort, dass ich mir keine Sorgen um das Scheunentor machen soll, wenn das Pferd bereits durchgebrannt ist.

Die Realität ist, dass es wirklich keine kostengünstige Möglichkeit gibt, eine gute Testabdeckung für ein Altsystem zu erhalten, schon gar nicht in einem kurzen Zeitraum. Wie MattNz bereits erwähnte, wird dies ein sehr zeitaufwendiger und letztendlich extrem kostspieliger Prozess sein. Mein Bauch sagt mir, dass Sie, wenn Sie versuchen, das Management zu beeindrucken, wahrscheinlich einen neuen Wartungs-Alptraum schaffen werden, weil Sie versuchen, zu schnell zu zeigen, ohne die Anforderungen zu verstehen, auf die Sie testen möchten.

Wenn Sie den Code erst einmal geschrieben haben, ist es realistisch gesehen fast zu spät, um die Tests effektiv zu schreiben, ohne das Risiko, dass Sie etwas Wichtiges verpassen. Auf der anderen Seite könnte man sagen, dass einige Tests besser sind als keine Tests, aber wenn dies der Fall ist, müssen die Tests selbst zeigen, dass sie dem gesamten System einen Mehrwert verleihen.

Mein Vorschlag wäre, sich die Schlüsselbereiche anzusehen, in denen Sie das Gefühl haben, dass etwas "kaputt" ist. Damit meine ich, es könnte furchtbar ineffizienter Code sein, oder Sie können nachweisen, dass die Wartung bisher sehr kostspielig war. Dokumentieren Sie die Probleme und verwenden Sie diese als Ausgangspunkt, um eine Teststufe einzuführen, die Ihnen hilft, das System zu verbessern, ohne einen massiven Neuentwicklungsaufwand in Kauf zu nehmen. Hier geht es darum, dass Sie nicht mit den Tests aufholen, sondern Tests einführen, die Sie bei der Lösung bestimmter Probleme unterstützen. Überprüfen Sie nach einiger Zeit, ob Sie die vorherigen Kosten für die Wartung dieses Codeabschnitts und den aktuellen Aufwand für die von Ihnen mit den unterstützenden Tests angewendeten Fixes messen und unterscheiden können.

Denken Sie daran, dass das Management mehr an den Kosten / Nutzen interessiert ist und wie sich dies direkt auf die Kunden und letztendlich auf das Budget auswirkt. Sie sind nie daran interessiert, etwas zu tun, nur weil es das Beste ist, wenn Sie nicht nachweisen können, dass es ihnen einen Nutzen bringt, der für sie von Interesse ist. Wenn Sie nachweisen können, dass Sie das System verbessern und eine gute Testabdeckung für die derzeit von Ihnen ausgeführte Arbeit erhalten, ist dies für das Management eher eine effiziente Anwendung Ihrer Bemühungen. Dies könnte es Ihnen möglicherweise ermöglichen, zu argumentieren, dass Sie Ihre Bemühungen auf andere Schlüsselbereiche ausdehnen, ohne entweder ein vollständiges Einfrieren der Produktentwicklung zu fordern, oder noch schlimmer, es ist fast unmöglich, sich für ein Umschreiben auszusprechen!


1

Eine Möglichkeit, die Abdeckung zu verbessern, besteht darin, weitere Tests zu schreiben.

Eine andere Möglichkeit besteht darin, die Redundanz in Ihrem Code so zu verringern, dass vorhandene Tests tatsächlich redundanten Code abdecken, der derzeit nicht abgedeckt ist.

Stellen Sie sich vor, Sie haben 3 Codeblöcke, a, b und b ', wobei b' ein Duplikat von B ist (exakte oder fast unpassende Kopie), und Sie haben Deckung über a und b, aber nicht b 'mit Test T.

Wenn Sie die Codebasis refaktorieren, um b 'zu eliminieren, indem Sie die Gemeinsamkeit von b und b' als B extrahieren, sieht die Codebasis jetzt wie folgt aus: a, b0, B, b'0, wobei b0 den nicht gemeinsam genutzten Code mit b'0 enthält, und umgekehrt. umgekehrt, und sowohl b0 als auch b'0 sind viel kleiner als B und rufen / verwenden B auf.

Jetzt hat sich die Funktionalität des Programms nicht geändert, und es wurde auch kein Test T ausgeführt, sodass wir T erneut ausführen können und erwarten, dass es erfolgreich ist. Der Code, der jetzt behandelt wird, ist a, b0 und B, aber nicht b'0. Die Codebasis ist kleiner geworden (b'0 ist kleiner als b '!) Und wir decken immer noch das ab, was wir ursprünglich abgedeckt haben. Unser Deckungsgrad ist gestiegen.

Dazu müssen Sie b, b 'und idealerweise B finden, um Ihr Refactoring zu ermöglichen. Unser CloneDR Tool kann dies für viele Sprachen tun, insbesondere für Java. Sie sagen, Ihre Codebasis enthält viele doppelte Codes. Dies könnte ein guter Weg sein, um es zu Ihrem Vorteil anzugehen.

Seltsamerweise erweitert der Akt des Findens von b und b 'häufig Ihren Wortschatz über die abstrakten Ideen, die das Programm implementiert. Während das Tool keine Ahnung hat, was b und b 'tun, gibt die bloße Isolierung aus dem Code, die eine einfache Konzentration auf den Inhalt von b und b' ermöglicht, Programmierern oft eine sehr gute Vorstellung davon, welche Abstraktion B_abstract der geklonte Code implementiert . Dies verbessert auch Ihr Verständnis des Codes. Vergewissern Sie sich, dass Sie B einen guten Namen geben, wenn Sie darauf verzichten, um eine bessere Testabdeckung und ein besser wartbares Programm zu erhalten.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.