
ABAP OO - Injector
Beim letzten Artikel hatten wir uns ABAP OO und die Evolution der Design Pattern angeschaut und wie wir sie am besten nutzen können. Daher gehen wir in diesem Artikel noch einmal in die Details des Injector für die Testbarkeit.
Inhaltsverzeichnis
In diesem Artikel schauen wir uns die Funktionsweise des Injectors und wie du diesen zusammen mit der Factory verwenden kannst, um einfache Testbarkeit zu ermöglichen.
Einleitung
Im letzten Blog der Serie hatten wir uns die Factory und den Singleton einmal im Detail angeschaut. Dort haben wir uns die verschiedenen Stufen der Evolution angeschaut und wie wir das Pattern weiter ausbauen können. Diesen Artikel solltest du als Grundlage lesen, bevor du mit diesem Artikel weiter machst. Dabei wollen wir nun die Factory nutzen und diese für unsere Unit Tests vorbereiten, ohne das eigentliche Coding in der Implementierung ändern zu müssen.
Herausforderung
Aktuell haben wir eine globale Klasse im System, die einen Zeitstempel validieren soll. Die Klasse erzeugt über die Factory eine Instanz unserer Implementierung und gibt sie in die Methode. Dort prüfen wir gegen einen spezifischen Zeitstempel in der Zukunft. Schaust du dir das Coding an, dann müssten wir aber warten, bis der Unit Test einmal funktioniert.
CLASS zcl_bs_demo_oo_test DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
METHODS validate_timestamp
RETURNING VALUE(result) TYPE abap_boolean.
ENDCLASS.
CLASS zcl_bs_demo_oo_test IMPLEMENTATION.
METHOD validate_timestamp.
DATA(timestamp) = zcl_bs_demo_oo_factory=>create( ).
RETURN xsdbool( timestamp->get_timestamp( ) = '20991231063000.0000000' ).
ENDMETHOD.
ENDCLASS.
Im Moment haben wir also unsere Komponente mit einem Unit Test. Zur Laufzeit ruft der Unit Test die Methode auf, die dann über die Factory eine Instanz zurückbekommt. Allerdings wird unser Unit Test erst in viele Jahren funktionieren und dann nur für einen sehr kleinen Bruchteil. Damit ist die Komponente erst einmal nicht gut testbar.
Erweiterung
Dazu erweitern wir das Szenario der Factory und der bisherigen Objekte um eine weitere Dimension. In diesem Kapitel schauen wir uns die verschiedenen Bestandteile an und erweitern sie.
Grundlage
Aus dem letzten Artikel benötigen wir die folgenden Objekte, wobei wir für unseren Fall nur die Factory verändern werden:
- ZCL_BS_DEMO_OO_FACTORY
- ZCL_BS_DEMO_OO_PRIVATE
- ZIF_BS_DEMO_OO_INTERFACE
Injector
Wir müssen nun zwei Anpassungen gleichzeitig durchführen, dazu legen wir uns im ersten Schritt den neuen Injector ZCL_BS_DEMO_OO_INJECTOR als globale Klasse an.
CLASS zcl_bs_demo_oo_injector DEFINITION
PUBLIC ABSTRACT FINAL
CREATE PUBLIC
FOR TESTING.
PUBLIC SECTION.
CLASS-METHODS inject_double
IMPORTING double TYPE REF TO zif_bs_demo_oo_interface OPTIONAL.
ENDCLASS.
CLASS zcl_bs_demo_oo_injector IMPLEMENTATION.
METHOD inject_double.
zcl_bs_demo_oo_factory=>double = double.
ENDMETHOD.
ENDCLASS.
Der Injector hat mehrere Eigenschaften, die für die Verwendung und Nutzung wichtig sind:
- ABSTRACT - Die Klasse soll nicht instanziierbar sein, sondern nur über die bereitgestellten Methoden und Attribute funktionieren.
- FOR TESTING - Die Klasse soll nur im Falle eines Unit Tests verwendbar sein. Grundsätzlich wird in Test- und Produktivsystemen der Code nicht kompiliert, das aber als zusätzliches Sicherheitsnetz.
- OPTIONAL - Der Double ist statisch und sollte für weitere Unit Tests auch löschbar sein. Daher setzen wir den Parameter auf Optional.
Factory
Im nächsten Schritt passen wir unsere Factory an. Als ersten Schritt definieren wir den Injector als GLOBAL FRIEND, damit dieser auch auf geschützte Methoden und Attribute zugreifen kann. Als Zweites definieren wir ein statisches Attribut, dass zur Laufzeit den DOUBLE enthält und als letzten Schritt sorgen wir dafür, dass die CREATE Methode uns den Double gibt, wenn dieser gesetzt wurde.
CLASS zcl_bs_demo_oo_factory DEFINITION
PUBLIC ABSTRACT FINAL
GLOBAL FRIENDS zcl_bs_demo_oo_injector.
PUBLIC SECTION.
CLASS-METHODS create
RETURNING VALUE(result) TYPE REF TO zif_bs_demo_oo_interface.
PRIVATE SECTION.
CLASS-DATA double TYPE REF TO zif_bs_demo_oo_interface.
ENDCLASS.
CLASS zcl_bs_demo_oo_factory IMPLEMENTATION.
METHOD create.
IF double IS INITIAL.
RETURN NEW zcl_bs_demo_oo_private( ).
ELSE.
RETURN double.
ENDIF.
ENDMETHOD.
ENDCLASS.
Zum Abschluss nicht vergessen beide Objekte (Injector und Factory) zu aktivieren.
Test
Wir haben nun alles vorbereitet für unseren Test. Schauen wir uns nun das aktuelle Szenario an und zwei Varianten, wie wir den Injector sinnvoll nutzen können.
Funktion
Unser Injector ist nun vorbereitet, damit wir über diesen ein Test Double in die Factory bringen können, welches wir dann im Unit Test verwenden. Damit testen wir nur noch das Verhalten unserer Methode VALIDATE_TIMESTAMP und nicht mehr die Abhängigkeit zur Implementierung in der Timestamp Klasse.
Lokale Klasse
Als erste Variante definieren wir uns eine lokale Hilfsklasse, wo wir das Interface implementieren. Da wir die Klasse als FOR TESTING gekennzeichnet haben, können wir PARTIALLY IMPLEMENTED verwenden und müssen nicht alle Methoden aus dem Interface implementieren.
CLASS lhc_double DEFINITION FOR TESTING.
PUBLIC SECTION.
INTERFACES zif_bs_demo_oo_interface PARTIALLY IMPLEMENTED.
ENDCLASS.
CLASS lhc_double IMPLEMENTATION.
METHOD zif_bs_demo_oo_interface~get_timestamp.
RETURN '20991231063000.0000000'.
ENDMETHOD.
ENDCLASS.
Bevor wir dann unseren Testcode ausführen, verwenden wir den Injector, erzeugen eine neue Instanz unserer Hilfsklasse und übergeben die Instanz. Der Injector setzt dann den DOUBLE in der Factory, welcher für den Unit Test verwendet wird.
zcl_bs_demo_oo_injector=>inject_double( NEW lhc_double( ) ).
Test Double Framework
Als zweite Variante können wir das Test Double Framework verwenden. Dabei brauchen wir die lokale Implementierung mit dem Interface nicht, müssen aber einige Schritte ausführen, um den Double vorzubereiten. Weitere Informationen zum Framework bekommst du in diesem Artikel.
DATA(double) = CAST zif_bs_demo_oo_interface( cl_abap_testdouble=>create( 'zif_bs_demo_oo_interface' ) ).
cl_abap_testdouble=>configure_call( double )->ignore_all_parameters( )->returning( '20991231063000.0000000' ).
double->get_timestamp( ).
Als letzten Schritt übergeben wir den erstellten und vorbereiteten Double an den Injector und führen wieder unseren Test aus.
zcl_bs_demo_oo_injector=>inject_double( double ).
Ergebnis
Beide Unit Tests sind nun lauffähig und wir erreichen den Timestamp doch eher und öfters, um unsere Unit Tests auf Grün zu bekommen.
Komplettes Beispiel
Den kompletten Quellcode der Beispiele findest du im entsprechenden GitHub Repository von uns. Das meiste Coding wirst du in diesem Artikel finden, vor allem bei der Ausführung und Verwendung haben wir aber nur die wichtigsten Teile gezeigt.
Fazit
In diesem Artikel wollten wir den Injector näherbringen und wie du ihn in zukünftigen Projekten einfach und effektiv nutzen kannst, um deinen Code testbar zu bauen und für andere Entwickler zur Verfügung zu stellen.


