Posted By da Agency
Legacy Code Refactoring in der Testautomatisierung




  • Als Softwareentwickler sind Sie vermutlich mit den Buzzwords Legacy Code und Refactoring vertraut. In der Testautomatisierung (TA) sind diese Begriffe seltener anzutreffen. Und das obwohl viele Testautomatisierungs-Setups einen beträchtlichen Anteil an Legacy Code enthalten, für den ein Refactoring längst überfällig wäre.

    Manchmal haben wir Testautomatisierer das Glück, die „Grüne Wiese“ bearbeiten zu dürfen. Ein Automatisierungs-Framework von Beginn an zu betreuen, hat oft seine angenehmen Seiten: Die Auswahl des Technologie-Stacks vorzunehmen, die Struktur und Architektur des Projekts definieren zu können und noch vieles mehr. Genau wie in der Softwareentwicklung. Aber auch hier gibt es Parallelen: Ebenfalls wie in der Softwareentwicklung kommt es viel häufiger vor, mit bestehenden Testfällen arbeiten zu müssen. Was machen wir also, wenn wir zu einem TA-Framework kommen, das bereits in die Jahre gekommen ist?

    Das häufigste und gleichzeitig auch verheerendste Symptom einer Legacy-Testautomatisierung: Testfälle funktionieren nicht mehr. Im schlimmsten Fall schlagen vielleicht sogar alle Testfälle fehl. Oder sie sind instabil und funktionieren vielleicht heute, morgen aber möglicherweise nicht mehr.

    In diesen Momenten ist es wichtig, Prinzipien und Herangehensweisen für eine langlebige Testautomatisierung zu kennen und sie während der Entwicklung (und auch der Wartung und Weiterentwicklung) zu berücksichtigen. Die folgenden Prinzipien helfen immer, egal ob Sie ein Projekt von Beginn an begleiten oder erst zu einem späteren Zeitpunkt betreuen dürfen.

    Was ist veralteter (legacy) Sourcecode?

    Die Definition besagt: Sourcecode der auf Teile einer Software zugreift, die nicht mehr unterstützt werden oder nicht mehr aktuell 

    Das trifft auch auf Testfälle und Testframeworks zu. Oft sind dort sogenannte Testing- und Code-Smells zu finden. Aber starten wir zuerst mit der Analyse.

    Analyse der Testfälle

    Auch Testfälle können altern. Was können wir also tun, wenn wir vor der Aufgabe stehen veraltete Testfälle oder ganze TA-Frameworks lauffähig zu machen?

    Zuerst sollten wir uns klarerweise einen Überblick über die aktuellen Testfälle verschaffen.

    Es könnte durchaus sein, dass Testfälle durchgeführt werden, die auf veraltete Systeme zugreifen, die unter Umständen schon durch neue Systeme ersetzt wurden oder überhaupt inhaltlich und fachlich nicht mehr relevant sind. Die Testfälle, die wir als nicht mehr relevant ausfindig gemacht haben, sind natürlich die einfachsten – sie werden ganz einfach entfernt.

    Sie sollten sich auch immer die Frage stellen:

    • „Hat dieser Testfall noch einen Wert für die Qualitätssicherung (Quality Assurance, kurz QA), die Entwicklung und für das Projekt?“
    • „Hilft er in der Regression, kostet die Wartung mehr als er wert ist?“

    Kleiner Tipp am Rande: Vor dem Entfernen immer mit dem Fachbereich abstimmen und begründen, warum die Testfälle entfernt werden sollten.

    Was machen wir aber nun mit den Testfällen, die für uns einen Wert haben? Die als sinnvoll erachteten Testfälle müssen aus mehreren Blickwinkeln betrachtet werden, um ihre Funktionalität weiterhin sicherzustellen:

    • Analysieren der Stabilität der Testumgebungen
    • Check der Testdaten auf Aktualität
    • Überprüfung der Testfälle auf fachliche Fehler 

    Analyse Testumgebung und Testdaten

    Es kann immer wieder vorkommen, dass Testdaten (z.B.: Benutzername/Passwort, Testkundenprofile, Wertelisten, etc.) nicht mehr zu den Testfällen oder zum System passen, das gerade getestet wird. Daher sollten Sie nach der Analyse der Testfälle auch die Umgebung und die Daten unter die Lupe nehmen.

    Das Hauptaugenmerk sollte hier ganz klar auf einer für die Testautomatisierung stabilen Umgebung und auf Testdaten liegen, die im besten Fall nur von der TA benutzt werden. So wird vermieden, dass Entwickler, manuelle Tester, TA und Fachbereiche bei Verwendung der gleichen Testdaten sich gegenseitig behindern oder sogar falsche Ergebnisse verursachen. Wenn Sie dann noch die Möglichkeit haben aus dem TA-Framework heraus eine eigene Umgebung aufzusetzen, dann haben Sie den Jackpot geknackt. Oft scheint das aber wie Zukunftsmusik.

    In dieser Phase kann es sich ebenfalls anbieten, dass Sie sich wieder Unterstützung aus dem Fachbereich organisieren. Oft resultiert dies in einfachen Konfigurationsanpassungen der Testfälle, die zu einem positiven und stabileren Ergebnis führen.

    Haben Sie die Testfälle, Testumgebung und Testdaten geprüft, können Sie sich auf den Sourcecode selbst konzentrieren.

    Analyse Sourcecode

    Was sollen wir beim Sourcecode selbst beachten? Nun ja, da gibt es viele Punkte und sie gelten im Grunde für die TA ebenso, wie für die restliche Softwareentwicklung. Des Öfteren habe ich Aussagen gehört wie „Testautomatisierer sind keine Softwareentwickler“, meist in einem negativen Aspekt und ich stimme dieser Aussage auf einer einzigen Art zu:

    Testautomatisierer sind mehr als nur Softwareentwickler. Testautomatisierer sind QA-nahe und qualitätsbewusste Softwareentwickler, die sich als Ziel gesetzt haben die Qualität des Systems unter Tests zu steigern!%MCEPASTEBIN%

    Um dieses Ziel zu erreichen, wollen wir langlebige TA-Frameworks und Testfälle mit nachhaltigem Wert schreiben.

    Ein paar Schlagwörter wie Clean Code, S.O.L.I.DDon’t Repeat Yourself (D.R.Y.), „Keep it simple, stupid“ (K.I.S.S.), Testing/Code Smells oder Law of Demeter sollten in jedes TA-Framework einfließen und werden unter anderem im Programm ICAgile vermittelt. Einige der erwähnten Punkte werden nachfolgend im Detail beschrieben. Übrigens, Nagarro bietet dieses Training in Wien an.

    Statische Code Analyse (Code Smells)

    Die Statische Code Analyse ist einer der wichtigsten Punkte, um seinen Sourcecode auf sogenannte Code Smells zu überprüfen.

    Unterschiedliche Tools, IDEs und Programmiersprachen bieten unterschiedliche Möglichkeiten diese Code Smells zu identifizieren.

    In .NET Projekten kann ich jedem nur wünschen, dass er ReSharper als ultimative Waffe gegen Code Smells und zur Unterstützung im Refactoring einsetzen kann. Sollte jemand die Extension noch nicht einsetzen, dann ist jetzt der richtige Zeitpunkt, um mit einem Scrum Master oder Projektleiter darüber zu sprechen.

    Was sind Code Smells?

    Einfach gesagt, ist jeder Sourcecode der es schwieriger macht ein Projekt zu Warten und zu Verstehen ein Code Smell. Anbei ein paar Auszüge:

    • Kommentare
    • Komplexität
    • Code Duplikate
    • Toter Code
    • Unkommunikative Namen
    • Inkonsistente Namen
    • Große Klassen
    • Lange Methoden
    • Viele Übergabeparameter

    Kommentare

    Eine einfache Art und Weise Sourcecode/Testfälle/Unittests unverständlich zu machen ist es sie mit nichts aussagenden oder irreführenden Kommentaren zu beschreiben.

    • Ein Fall, bei dem Kommentare grundsätzlich Sinn machen ist bei APIs, die für Dritte zur Verfügung gestellt werden müssen.
    • Der zweite Fall sind zusätzliche Informationen, die eine Funktionalität beschreiben, die aber nicht vom Sourcecode selbst beschrieben werden.
    • Warum wurde für diese Methode diese Herangehensweise gewählt?
    • Worauf ist zu achten, wenn die Methode verwendet werden möchte?

    In allen anderen Fällen sind Kommentare nichts weiteres als Code Smells und sollten entfernt werden. Auch wenn sie Beispielsweise als „Message“ in einem Assert verwendet werden.

    Wenn ich des Öfteren auskommentierte Code-Stellen entdecke, frage ich immer: Gibt es einen Grund warum die Stelle auskommentiert und nicht gleich entfernt wurde? In den meisten Fällen hört ihr dann Antworten wie etwa: „Ja, der Code soll nicht verloren gehen!“ oder „Ja, vielleicht brauchen wir den Code noch einmal!“

    Auch wenn diese Aussagen früher womöglich teilweise stimmen durften, in Zeiten von Git/Subversion oder ähnlichem gelten diese Gründe nicht mehr. Wir verwenden keine Disketten mehr, um Sourcecode zu sichern. Wir können zu jedem erdenklichen Zeitpunkt dorthin zurück, wo der alte Code umgeschrieben wurde. Es gibt also keinen Grund Sourcecode auszukommentieren.

    Bitte sprechen Sie mit Ihrem Team, löschen Sie diese Stellen und verweisen auf Ihre SCM-Tools.

    Komplexität

    In den 90er Jahren, als die Softwareentwicklung festgestellt hat, dass Klassen-Ableitungen verwendet werden können, um Abhängigkeiten zu minimieren, haben einige dieser Softwareentwickler auch relativ schnell feststellen müssen, dass die Komplexität des Sourcecodes dadurch erheblich gesteigert wird.

    Umso verschachtelter die Ableitungen werden, umso schwieriger wird es für das menschliche Gehirn die Software zu verstehen. Daher sollten Sie sich ganz genau überlegen, ob und wofür Sie Ableitungen verwenden wollen. Da Ableitungen grundsätzlich etwas Gutes sein können, sollten Sie versuchen bei maximal 3-4 Ableitungen zu bleiben.

    Das bedeutet, sollten Sie Verschachtelungen entdecken, sollten Sie versuchen die Klassen zusammenzuziehen – wo möglich.

    Code Duplikate

    Umso mehr Sourcecode wir schreiben umso wahrscheinlicher ist es, dass wir gewisse Code-Stellen an mehreren Stellen benötigen. Die einfachste Art und Weise kennen wir alle, Strg+C/Strg+V. Das ist aber auch die schlechteste Variante.

    Don’t repeat yourself (DRY)
    Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.
     

    Ok, ok, der eine oder andere unter Ihnen wird nun sagen, dass diese Auslegung sehr strikt ist. In manchen Fällen sind wir als Software-Architekten und Entwickler auch bereits darauf gekommen, dass dieses Prinzip nicht immer Sinn macht. Und ja, ich gebe Ihnen Recht. Microservices sind so ein Beispiel. Nichtsdestotrotz ist es ein Ziel das wir in einem TA-Umfeld, in einem TA-Framework, verfolgen sollten.

    Gleiche Code-Stellen sollten rausgezogen werden. Die Wartbarkeit wird dadurch merklich erhöht.

    Toter Code

    Genau wie bei den Kommentaren sollte unnötiger Sourcecode rückstandslos entfernt werden. Stellen die nicht erreicht werden können sind oft deshalb noch vorhanden, weil irgendwo Sourcecode auskommentiert wurde und der Rest einfach übriggeblieben ist.

    Und damit haben wir auch schon den Grund, warum wir diese Stellen entfernen sollten.

    Unkommunikative und inkonsistente Namen

    Eine der effektivsten Arten seinen Code aufzuräumen ist es Methoden, Klassen, Variablen oder anderen Sourcecode sinnvoll zu benennen. In Ihrer Software sollten Sie sprechende Namen für einfach alles verwenden. Es gibt keinen Grund statt „Controller“ „Ctrl“ oder statt „Count“ „L“ zu verwenden – außer vielleicht Faulheit. Und das sollten wir als QA-nahen Softwareentwickler nicht als Grund gelten lassen.

    Genau wie bei unseren Methoden, sollten wir natürlich auch auf die konsistente Benennung unserer Testfälle achten. Es gibt nichts mehr Verwirrendes, als das ähnliche Code-Teile unterschiedlich benannt werden. Wenn Sie sich auf eine Bezeichnung geeinigt habt, dann bleiben Sie dabei. Führen Sie als Hilfestellung ein Glossar ein, das neuen Entwicklern helfen soll sich im Projekt zurecht zu finden.

    Sollten Sie übrigens in Ihren Namen auf Wörter wie „And“ stoßen, würde ich Sie gerne auf das erste Prinzip von S.O.L.I.D. hinweisen.

    Single responsibility principlev (SRP)
    A class should have one, and only one, reason to change.%MCEPASTEBIN%

    Es sollte nur einen Grund geben, warum Klassen, Methoden oder Testfälle existieren. Das Bindewort „And“ deutet immer darauf hin, dass diese Stelle nicht aus einem einzigen Grund implementiert wurde. Teilen Sie Ihre Testfälle auf. Das gleiche gilt übrigens auch für Assertions. Mehr als ein Assert führt dazu, dass es schwieriger wird den Testfall zu warten, zu überprüfen und im Fehlerfall macht es den Testfall schwierig zu interpretieren.

    Große Klassen

    Große Klassen weisen immer darauf hin, dass sie sich nicht an das Single responsibility principle halten. Es gibt aber auch noch andere Gründe, warum Klassen an Größe gewinnen. Zwei davon sind ebenfalls in S.O.L.I.D. zu finden.

    Open/closed principle (OCP)
    Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification.%MCEPASTEBIN%

    Eine Klasse soll immer erweiterbar sein, ohne die Klasse selbst zu ändern. Das wiederum erreichen Sie indem Sie die Klassen in Interfaces trennen, Abstraktionen einführen und nicht direkt vom konkreten Implementieren ableiten oder sie verwenden. Die Erweiterung davon beschreibt auch das Dependency inversion principle.

    Dependency Inversion principle (DIP)

    • High-level modules should not depend on low-level modules. Both should depend on abstractions.
    • Abstractions should not depend on details. Details should depend on abstractions

     Short: Depend upon abstractions, [not] concretions

    Ich denke dieses Prinzip ist uns in der TA allen bekannt und wenn nicht, dann sollten Sie sich das Prinzip ganz genau ansehen. Ohne dieses Prinzip könnten wir kaum eine Testautomatisierung durchführen. Vor allem nicht auf Unit Test oder Integration Test Basis.

    Lange Methoden

    Neben den üblichen Verdächtigen (z.B.: SRP) möchte ich bei den langen Methoden außerdem auf das K.I.S.S. (Keep it simple, stupid) Principle hinweisen.

    Keep it simple, stupid (KISS)
    The K.I.S.S. principle states that most systems work best if they are kept simple rather than made complicated%MCEPASTEBIN%

    Es ist nicht notwendig, komplizierte Konstrukte und Design Pattern zu implementieren, wenn es dafür überhaupt keine Anforderungen gibt. Entwickler versuchen oft bereits vorab zu bedenken, wo könnte sich dieses System hin entwickeln. Und ja, das ist grundsätzlich auch gut. Es ist jedoch nicht notwendig, jede kleinste Wahrscheinlichkeit auch gleich zu implementieren.

    K.I.S.S. weist also auch darauf hin, dass Themen erst dann umgesetzt werden sollten, wenn sie benötigt werden und nicht bereits von Anfang an. Dies wiederum entspricht auch dem agilen Mindset und wir als Testautomatisierer sollten das unbedingt bedenken.

    Viele Übergabeparameter

    Sollten Sie darauf stoßen, dass Ihre Constructor oder Methoden mehr als 2 Aufrufparameter haben, überlegen Sie sich die Parameter in ein eigenes Objekt auszulagern. Das wiederrum erhöht die Lesbarkeit und die Möglichkeit das System unter Test zu bringen.

    Fazit

    Die genannten Beispiele sind nur ein kleiner Bereich dessen, was Sie im täglichen Arbeiten an Sourcecode beachten sollten. Es soll Sie dazu anregen sich mit diesem Thema etwas intensiver zu beschäftigen. Es macht unter dem Strich Ihr Leben und das Ihres Teams etwas leichter.
    Wenn wir uns nun all diese Ratschläge ansehen ergibt sich mein Fazit eigentlich bereits von selbst.

    Sollten Sie an einem Projekt arbeiten, bei dem Sie die genannten Themen entdecken, dann nichts wie „Ran an den Speck!“.

    • Reden Sie mit Ihrem Team, Ihrem Projektleiter.
    • Verschaffen Sie sich Gehör.
    • Erklären Sie ihnen das Vorhaben.
    • Und dann „Happy Refactoring“.

    Tipp: Das bedeutet übrigens nicht, dass Sie immer ein großes Refactoring bei einem Projekt machen müssen. Und damit noch ein letztes Prinzip: Machen Sie es wie die Pfadfinder (Boy-scout). Die Boy-scout Rule besagt: „Kommt ihr an einen Platz (z.B.: Klasse, Method etc.) an dem ihr etwas vorfindet das nicht so sein sollte (z.B: Code Smells), dann hinterlasst den Platz schöner als ihr ihn vorgefunden habt.“ Kurz: Räumen Sie auf, der nächste Boy-scout wird es Ihnen danken!

    Jürgen Pointinger

    Der Beitrag Legacy Code Refactoring in der Testautomatisierung erschien zuerst auf Austrian Testing Board.

    Jetzt Testing Trainingskurse, wie Aufwandsschätzung für Tester, Aufwandsschätzung für Testmanager, Effizientes Testen durch Testprozessoptimierung, Fehler- und Abweichungsmanagement, IT-Effizienz steigern: Produkte, Prozesse, Kosten, Kennzahlen für Testmanager, Performance Testing: Advanced, Testkonzepte schreiben, Testmanagement bei Abnahmetest, Testoutsourcing, Testumgebung – das Management, Usability: Gebrauchstauglichkeit von IT-Systemen systematisch bewerten bei einer