This is a test message to test the length of the message box.
Login
|

042: Modern, solid and testable ABAP Code (Part 3)

56

Die digitale Version des betterCode Vortrags zum Thema moderner und testbarer ABAP Code. Dabei schauen wir uns das Thema Software Architektur an und geben Tipps für die Nutzung von ABAP Unit.

Werbung


Willkommen zur dritten Folge von "Modern, solid and testable ABAP Code". In dieser Folge werden wir in das Thema ABAP Unit gehen. Dabei haben wir in den beiden Folgen zuvor uns die verschiedenen Pattern angeschaut, die wir auch für eine testbare Architektur benötigen und haben uns in der letzten Folge vor allem Beispiele aus dem Standard angeschaut.

 

ABAP Unit

Werfen wir im ersten Schritt einen Blick auf ABAP Unit und die Frage, warum wir eigentlich mit Unit Tests arbeiten sollten. Auf der einen Seite haben wir natürlich die Möglichkeit, keine Unit Tests zu schreiben. Das bedeutet aber im Gegenzug, dass wir einen sehr hohen manuellen Aufwand haben, um die verschiedenen Komponenten jedes Mal wieder zu testen und zu validieren. Dabei würden wir im manuellen Prozess niemals jede einzelne Komponente isoliert prüfen. Das heißt, bestimmte Bestandteile unseres Codes würden wahrscheinlich nie oder nur selten getestet werden. Daraus ergibt sich das konkrete Risiko, ungetesteten Code in die Produktion zu bringen, der uns später schwerwiegende Probleme bereiten könnte. Gleichzeitig wird Code im Lebenszyklus einer Anwendung nicht immer durch denselben Entwickler geändert. Sollte einmal ein anderer Kollege unseren Code erweitern oder modifizieren müssen, besteht ohne automatisierte Tests eine gewisse Hemmschwelle, und oft auch die konkrete Angst, diesen Code überhaupt anzufassen. Da die tieferen Zusammenhänge komplexer Bestandteile ohne Testabdeckung nicht sofort ersichtlich sind, lässt sich schwer vorhersagen, welche Abhängigkeiten man durch eine Änderung beschädigt oder beeinflusst. Das Risiko ist hoch, dass spätere Probleme in der Produktion auftreten, weil bestimmte Use Cases schlicht nicht bedacht oder vorab automatisiert geprüft werden konnten.

Auf der anderen Seite wiederum finden wir die Verwendung von Unit Tests. Das heißt, wir haben immer eine verlässliche Validierung gegen das erwartete Ergebnis einer Methode und können so direkt während der Entwicklung sehen, ob unsere Implementierung korrekt funktioniert oder nicht. Gleichzeitig erhalten wir durch eine ausreichende Abdeckung mit Unit Tests auf unserem Code ein stabiles Sicherheitsnetz. Dieses Sicherheitsnetz können wir nutzen, um spätere Änderungen oder Refactorings durchzuführen. Die Tests geben uns sofort Rückmeldung, ob die bestehende Logik weiterhin wie gewünscht funktioniert oder ob wir durch die Anpassungen unbemerkt Fehler eingebaut haben. Das gilt sowohl für die eigene Weiterentwicklung als auch für die Arbeit durch Dritte. Ein weiterer entscheidender Vorteil ist die Erhöhung der Änderungsfrequenz. Da der zeitintensive manuelle Testaufwand entfällt, können wir in der gleichen Zeit deutlich mehr Anpassungen am Quellcode vornehmen und Releases in kürzeren Zyklen veröffentlichen. Die automatisierten Tests lassen sich schließlich mit nur einem Klick und innerhalb weniger Sekunden ausführen. Der dritte Punkt beschäftigt sich vor allem mit dem Testen spezieller Szenarien und Edge Cases. Wenn wir beispielsweise in der Produktion einen Fehler gemeldet bekommen, können wir genau dieses Szenario in einem Unit Test abbilden. Ab diesem Zeitpunkt wird der Fehler mit jeder zukünftigen Änderung automatisch mitgeprüft. In Zukunft müssen wir an diesen spezifischen Fall nicht mehr aktiv denken, da unsere Testfälle dauerhaft darauf aufbauen, dass genau dieses Problem nicht wiederkehrt. Denn nichts ist im Entwicklungsalltag ärgerlicher, als ein bereits behobener Fehler, der durch spätere Codeänderungen als Regression wieder in die Produktion gelangt. Genau hier spielen Unit Tests ihre Stärken aus. Der letzte Aspekt betrifft die Dokumentation der Funktionalität. Unit Tests fungieren als die beste Form der technischen Dokumentation für die Verwendung von Code, ohne dass dafür ein manueller Mehraufwand für die Pflege von Dokumenten entsteht. Ein dritter Entwickler sieht beim Blick auf die Testfälle sofort: Wie der Code aufgerufen werden muss. Wie die Eingangsparameter zu befüllen sind. Wie mit dem Ergebnis und den Rückgabewerten umzugehen ist.

 

Messwerte

Wie sieht es also konkret mit den Tests aus? Wie sollten wir diese aufbauen und welche Anzahl bzw. Testabdeckung ist wichtig? Diese Fragen sollten wir uns vorab selbst beantworten. Bei der Anzahl der Unit Tests ist darauf zu achten, dass wir nicht nur den sogenannten „Happy Path“, also rein positive Tests, schreiben, sondern natürlich auch negative Szenarien abbilden. Das bedeutet: Wie verhält sich unsere Implementierung, wenn die Schnittstellen mit fehlerhaften Informationen versorgt werden? Zusätzlich können wir Grenzwerte definieren, um beispielsweise zu prüfen, ob bei einem drohenden Überlauf bestimmte Exceptions ausgelöst werden. Damit ist am Ende der Großteil der Methode strukturell getestet. Als Mindeststandard sollten immer die positiven und negativen Basisszenarien abgedeckt sein, da die Validierung aller denkbaren Extremwerte natürlich ein entsprechender Aufwandstreiber bei der Erstellung von Unit Tests ist. Weiterhin sollten wir die Qualität der eigentlichen Tests kritisch prüfen. Dabei ist die Code Coverage nicht das alleinige Kriterium für hohe Qualität. Viel wichtiger ist, dass unsere Tests absolut wiederholbar und unabhängig von äußeren Einflüssen sind, um sie immer wieder schnell ausführen zu können und ein verlässliches Ergebnis zu erhalten. Dabei gilt die Regel: Die einzelnen Tests dürfen nicht zu lange laufen. Wir wollen sofortige, schnelle Ergebnisse erzielen; ein Unit Test ist schließlich keine Performance-Analyse. Zusätzlich sollten wir automatisierte Test-Suites nutzen, um Seiteneffekte auf andere Quellcodes und aufrufende Programme zu validieren. Grundsätzlich ist ein einzelner Unit Test zwar dafür da, das eigentliche, isolierte Objekt zu prüfen. Wenn jedoch alle deine RAP-Geschäftsobjekte, CDS-Hilfsklassen oder Verarbeitungslogiken eigene Unit Tests besitzen, kannst du diese Tests mit den entsprechenden Werkzeuge regelmäßig über alle Objekte hinweg ausführen. Sollten wir also Typen, Schnittstellen oder das Verhalten von Methoden ändern, sehen wir durch das Gesamtergebnis sofort, ob sich nutzende Komponenten ungewollt mit verändert haben. Das hilft vor allem für die Zukunft und für die Zeit vor der Produktivsetzung, um Fehler frühzeitig zu erkennen und zu fixen, bevor sie überhaupt in die Produktion gelangen.

 

Szenario

Schauen wir uns dazu einmal ein Szenario im Detail an. In diesem Fall verwenden wir die erzeugte Factory mit der entsprechenden Implementierung, wie wir sie aus unserer Vorübung aufgebaut haben. Dazu implementieren wir eine neue Komponente, welche die Factory, also unsere eigentliche Implementierung, nutzt, und wollen auf Basis dieser neuen Komponente einen Unit Test schreiben. Dies simuliert exakt das Verhalten, wenn wir unseren Kollegen eine entsprechende API zur Verfügung stellen, die sie in ihren eigenen Anwendungen nutzen, während wir gleichzeitig sicherstellen wollen, dass unsere Komponenten isoliert testbar bleiben.

Schauen wir uns die Klassen einmal im Detail an. Dabei nutzen wir wieder dasselbe Interface, welches wir vorab definiert haben. Wir haben eine implementierende Klasse, die über eine Factory instanziiert wird und entsprechend einen Zeitstempel erstellt. Dazu erstellen wir nun die eigentliche Implementierung unserer Klasse. Diese Klasse besitzt eine öffentliche Methode, die prüfen soll, ob wir den „richtigen Moment“ erreicht haben. Ist dies der Fall, geben wir abap_true zurück, andernfalls ein abap_false. In der eigentlichen Implementierung nutzen wir die Factory, um unsere API zu erzeugen. Über das Timestamp-Objekt holen wir uns den aktuellen Zeitstempel, um diesen zu vergleichen. Das Ergebnis dieser Validierung geben wir dann als Rückgabewert der Methode zurück. Hierbei handelt es sich um eine ziemlich einfache Funktion und etwas, das wir normalerweise so nicht für die Produktion schreiben würden. Denn das eigentliche Problem ist: Der „richtige Moment“ ist zeitlich extrem eingeschränkt und in der Realität kaum exakt zu treffen, es sei denn, wir würden mit einem logischen Zeitraum statt eines fixen Zeitstempels arbeiten.

In der Testklasse haben wir bereits die entsprechenden ABAP Unit Tests angelegt und dabei einen positiven sowie einen negativen Testfall abgebildet: Einmal haben wir den „richtigen Moment“ erreicht und einmal haben wir ihn nicht erreicht. Davon abgesehen liegt uns eine relativ einfache Implementierung vor, die wir nun einmal im Unit Test ausführen. Dazu verwenden wir das Code-Mining in den ABAP Development Tools, klicken auf den Run-Button und starten den eigentlichen Unit Test. Im Testergebnis sehen wir aktuell folgende Situation: Unser Testfall für das Szenario, dass der Moment nicht erreicht wurde, funktioniert einwandfrei und ist stabil testbar. Das liegt schlicht daran, dass wir den exakten, hartcodierten Moment im normalen Testlauf standardmäßig niemals treffen. Genau hier liegt aber auch das Problem: Wir können den positiven Testfall, der prüfen soll, wann der Moment erfolgreich erreicht ist, aktuell überhaupt nicht deterministisch testen. Da die Methode intern immer den echten, aktuellen Systemzeitstempel abfragt, haben wir im Unit Test keine Kontrolle über den gelieferten Wert. Diese fehlende Testbarkeit wollen wir nun beheben. Dafür werden wir unser Framework und die entsprechenden Objekte um eine Mocking-Möglichkeit erweitern.

 

Injector

Für die Lösung dieses Problems verwenden wir das sogenannte Injector-Pattern. Dieses Entwurfsmuster hatten wir bisher noch nicht vorgestellt, implementieren es aber nun speziell für diesen Use Case. Dabei erweitern wir unsere Architektur neben der bestehenden Factory um eine sogenannte Injector-Klasse. Diese Injector-Klasse soll dafür sorgen, dass wir zur Testlaufzeit die Instanziierung in der Factory manipulieren können. Das Ziel ist es, am Ende nicht die eigentliche, produktive Implementierung zurückzuerhalten, sondern ein von uns definiertes Test Double. Im Umkehrschluss bedeutet das für uns: Wir müssen keinerlei unsaubere Anpassungen oder Verzweigungen im produktiven Code vornehmen, um das Test-Szenario für die Factory zu ermöglichen. Das verhält sich absolut konform zu einer modernen, testbaren Softwarearchitektur im ABAP-Umfeld.

Im nächsten Schritt legen wir dazu ein sogenanntes Test Double an, beziehungsweise in diesem Fall ein Local Test Double, da wir die Implementierung lokal innerhalb unserer Testklasse abbilden. Dafür erzeugen wir eine lokale Klasse ltd_timestamp und definieren diese mit dem Zusatz FOR TESTING. Das FOR TESTING ist hierbei essenziell, da es dem Laufzeitsystem signalisiert, dass diese Klasse ausschließlich im Testkontext existiert und verwendet werden darf. Anschließend implementieren wir das Interface, welches wir auch in unserem produktiven Objekt nutzen. Dabei verwenden wir den Zusatz PARTIALLY IMPLEMENTED. Dieser Zusatz sorgt im ABAP dafür, dass wir nicht zwingend alle Methoden des Interfaces in unserer lokalen Testklasse ausprogrammieren müssen. Stattdessen können wir gezielt entscheiden, welche Methoden wir für unser spezifisches Test-Szenario benötigen. Das sehen wir gleich im Ergebnis: Wir können eine nicht benötigte Methode einfach weglassen und implementieren für uns ausschließlich die relevante Methode get_timestamp. Da wir uns im kontrollierten Testszenario befinden, lassen wir diese Methode nun genau den von uns vordefinierten Zeitstempel zurückgeben, den wir für die Validierung des positiven Testfalls benötigen.

Wir haben für diesen Use Case bereits einen entsprechenden Injector angelegt, den du ebenfalls in dem Paket findest. Diese Klasse ist als ABSTRACT FINAL definiert, damit von ihr keine Instanz erzeugt werden kann. Gleichzeitig wurde sie mit dem Zusatz FOR TESTING deklariert. Das bedeutet, dass der Injector ausschließlich in Unit Tests aufgerufen werden kann und im produktiven Code nicht verfügbar ist. Dies schirmt den Injector effektiv davor ab, dass andere Entwickler ihn fälschlicherweise im produktiven Kontext nutzen, um unerwünschte Ersatzinstanzen in unsere Factory einzuschleusen. Der Injector besitzt eine statische Methode für das Injizieren des Test Doubles. Diese Methode nimmt das Test Double entgegen und speichert es in der Factory. mDas bedeutet im Umkehrschluss jedoch auch, dass wir eine kleine Anpassung an der Factory selbst vornehmen müssen. In der globalen Factory-Klasse haben wir den Injector als GLOBAL FRIEND definiert. Dadurch erhält die Injector-Klasse das Recht, auf die privaten Attribute und Methoden der Factory zuzugreifen. Im privaten Bereich der Factory definieren wir ein statisches Attribut vom Typ des entsprechenden Interfaces. In diesem Attribut speichern wir später zur Testlaufzeit unser eigentliches Test Double. Schließlich passen wir die CREATE-Methode der Factory an. Hier prüfen wir nun zuerst, ob das Test Double gesetzt ist. Ist das der Fall, geben wir die Instanz des Doubles zurück. Andernfalls wird der normale produktive Code ausgeführt und die reguläre Instanz angelegt.

Damit das Test Double funktioniert, rufen wir den Injector auf, bevor wir unseren Unit Test starten, um die Instanz innerhalb der Factory für den Testlauf vorzubereiten. Dazu rufen wir die INJECT-Methode auf und übergeben eine neue Instanz unseres lokalen Doubles, das wir uns zuvor als lokale Klasse angelegt haben. Grundsätzlich ist es natürlich auch möglich, das offizielle Test Double Framework zu verwenden, da dieses ebenfalls Test Doubles dynamisch erzeugen kann. In diesem Fall haben wir es jedoch zum besseren Verständnis manuell gelöst, indem wir eine eigene, lokale Implementierung angelegt haben. So ist im Code direkt und transparent ersichtlich, welche Werte zurückgegeben werden. Beim Test Double Framework müssen wir entsprechende Vorkonfigurationen, wie das Einrichten der Erwartungshaltung und der Antwortwerte, vornehmen, was mehr Zeilen Code bedeutet und etwas erklärungsbedürftiger ist. Daher ist das integrierte Framework in diesem Vortrag kein Bestandteil.

Führen wir nun unsere Unit Tests aus, erhalten wir für beide Testfälle ein grünes Ergebnis. Das heißt, wir sind ab sofort in der Lage, auch das Szenario zu testen, in dem der „richtige Moment“ erreicht ist. Indem wir zur Testlaufzeit die eigentliche Implementierung über die Factory austauschen, greift die Anwendung auf unsere eigene Implementierung zu und gibt genau den von uns definierten Wert zurück. Das Entscheidende dabei ist: Dies ist möglich, ohne den produktiven Code durch unsaubere Test-Verzweigungen zu belasten. Wir nutzen stattdessen lediglich das Injector-Pattern als ein zusätzliches, sauberes Architekturwerkzeug für die testgetriebene Entwicklung im ABAP-Umfeld.

 

Ausblick

In dieser Folge haben wir uns das Testen mit den entsprechenden Design Pattern angeschaut und gelernt, wie wir ohne großen Aufwand mithilfe eines zusätzlichen Injector-Patterns die Testbarkeit unseres Codes gezielt herstellen können. Dabei solltest du mitbekommen haben, dass die entsprechende Vorbereitung der Factory eine hervorragende Grundlage bietet, um die Testbarkeit einfach und ohne Probleme zu gewährleisten. In der nächsten Folge schauen wir uns die verschiedenen Automatisierungsmöglichkeiten an, um Unit Tests in der Ausführung vollautomatisch laufen zu lassen und so noch schneller Ergebnisse als Rückmeldung aus dem System zu erhalten. Daher danke fürs Zuschauen ... und bis zum nächsten Mal.

 

Weitere Referenzen:
YouTube - Part 3
GitHub - Beispiele


Enthaltene Themen:
YouTubeSkriptModernABAPClean ABAP
Kommentare (0)



Und weiter ...

Bist du zufrieden mit dem Inhalt des Artikels? Wir posten jeden Dienstag und Freitag neuen Content im Bereich ABAP und unregelmäßig in allen anderen Bereichen. Schaue bei unseren Tools und Apps vorbei, diese stellen wir kostenlos zur Verfügung.


043: Modern, solid and testable ABAP Code (Part 4)

Kategorie - YouTube

Die digitale Version des betterCode Vortrags zum Thema moderner und testbarer ABAP Code. Dabei schauen wir uns das Thema Software Architektur an und geben Tipps für die Nutzung von ABAP Unit.

25.05.2026

041: Modern, solid and testable ABAP Code (Part 2)

Kategorie - YouTube

Die digitale Version des betterCode Vortrags zum Thema moderner und testbarer ABAP Code. Dabei schauen wir uns das Thema Software Architektur an und geben Tipps für die Nutzung von ABAP Unit.

11.05.2026

040: Modern, solid and testable ABAP Code (Part 1)

Kategorie - YouTube

Die digitale Version des betterCode Vortrags zum Thema moderner und testbarer ABAP Code. Dabei schauen wir uns das Thema Software Architektur an und geben Tipps für die Nutzung von ABAP Unit.

04.05.2026

038: Recycling-Heroes - Annotations (Document)

Kategorie - YouTube

In dieser Folge schauen wir uns die Annotationen der Dokumenten App an und wie wir diese sehr einfach anlegen können. Dazu erweitern wir die App und fixen ein Problem mit dem Schlüssel.

30.03.2026

037: Core Data Service [Basics] - View and View Entity

Kategorie - YouTube

Schauen wir uns einmal die klassische View im Unterschied zur modernen View Entity an. Dabei gehen wir auf kleinere Unterschiede und die Migration in ABAP ein und wie du Core Data Services einfacher handhaben kannst.

16.03.2026