This is a test message to test the length of the message box.
Login
ABAP Unit Testbarer Code
Erstellt von Software-Heroes

ABAP Unit - Testbarer Code (Teil 3)

Hier schauen wir uns die Möglichkeiten etwas genauer an, wie man abhängige Komponenten im eigenen Coding zur Testlaufzeit deaktivieren können.

Werbung

In diesem Artikel geht es um die Methodik der Dependency Injection, um verwendete Komponenten während des Tests zu deaktivieren und so den eigenen Code testbar zu machen.

 

Begriff

Hierbei geht es um die Technik, wie man mit abhängigen Komponenten umgeht und dem Tester bzw. Aufrufer die Möglichkeit gibt, solche Komponenten zu deaktivieren oder zu überschreiben.

Dazu einmal ein “schlechtes” Beispiel, bei dem der Aufrufer kaum eine Chance hat, die abhängige Komponente zu deaktivieren. Das Objekt wird in der Klasse instanziiert und direkt im Anschluss verwendet:

CLASS zcl_test_handle_docs DEFINITION
  PUBLIC
  FINAL
  CREATE PUBLIC.

  PUBLIC SECTION.
    METHODS:
      constructor,

      call_selector
        IMPORTING
                  id_flag          TYPE abap_bool
        RETURNING VALUE(rd_result) TYPE string.
ENDCLASS.


CLASS zcl_test_handle_docs IMPLEMENTATION.
  METHOD constructor.
  ENDMETHOD.

  METHOD call_selector.
    NEW zcl_test_doc( )->do_stuff_and_select_data( id_flag ).
  ENDMETHOD.
ENDCLASS.

 

An Hand dieses Beispiels wirst du nun verschiedene Methoden lernen, wie du die Komponente durch deine eigene Komponente austauschen kannst. Hierbei werden wir einen Test Double verwenden.

 

Vorbereitung

Damit die stark gekoppelte Klasse erst einmal in der Methode entkoppelt wird, muss die Klasse etwas umgebaut werden. Die Instanzvariable sollte als Membervariable (Attribut der Klasse) ausgelagert werden und die Instanziierung sollte entsprechend von der Verwendung getrennt werden. Nach dem Umbau würde die Klasse dann wie folgt aussehen:

CLASS zcl_test_handle_docs DEFINITION
  PUBLIC
  FINAL
  CREATE PUBLIC.

  PUBLIC SECTION.
    METHODS:
      constructor,

      call_selector
        IMPORTING
                  id_flag          TYPE abap_bool
        RETURNING VALUE(rd_result) TYPE string.

  PRIVATE SECTION.
    DATA:
      mo_doc TYPE REF TO zif_test_doc.  
ENDCLASS.


CLASS zcl_test_handle_docs IMPLEMENTATION.
  METHOD constructor.
    mo_doc = NEW zcl_test_doc( ).
  ENDMETHOD.

  METHOD call_selector.
    mo_doc->do_stuff_and_select_data( id_flag ).
  ENDMETHOD.
ENDCLASS.

 

Ebenfalls legen wir im lokalen Bereich eine Dummy Implementierung an, die den DOC austauscht und entsprechend unserer Logik zur Verfügung stellt. Am Namen “LTD_” (Local Test Double) erkennst du bereits, was der Sinn der Klasse ist.

CLASS ltd_doc DEFINITION FINAL.
  PUBLIC SECTION.
    INTERFACES: zif_test_doc.
ENDCLASS.


CLASS ltd_doc IMPLEMENTATION.
  METHOD zif_test_doc~do_stuff_and_select_data.
    " Empty implementation
  ENDMETHOD.
ENDCLASS.

 

Constructor Injection

Über den Konstruktor bieten wir einen optionalen Parameter an, der die DOC überschreibt. Wir kein eigenes Objekt übergeben, dann wird eine entsprechende Instanz der konkreten Klasse erzeugt.

CLASS zcl_test_handle_docs DEFINITION
  PUBLIC
  FINAL
  CREATE PUBLIC.

  PUBLIC SECTION.
    METHODS:
      constructor
        IMPORTING
          io_doc TYPE REF TO zif_test_doc OPTIONAL,      

      call_selector
        IMPORTING
                  id_flag          TYPE abap_bool
        RETURNING VALUE(rd_result) TYPE string.

  PROTECTED SECTION.
  PRIVATE SECTION.
    DATA:
      mo_doc TYPE REF TO zif_test_doc.  
ENDCLASS.


CLASS zcl_test_handle_docs IMPLEMENTATION.
  METHOD constructor.
    mo_doc = io_doc.

    IF mo_doc IS NOT BOUND.
      mo_doc = NEW zcl_test_doc( ).
    ENDIF.  
  ENDMETHOD.


  METHOD call_selector.
    mo_doc->do_stuff_and_select_data( id_flag ).
  ENDMETHOD.
ENDCLASS.

 

Die Testklasse mit der Anwendung der Methode sieht nun wie folgt aus:

CLASS ltc_constructor_injection DEFINITION FINAL FOR TESTING
  DURATION SHORT
  RISK LEVEL HARMLESS.

  PRIVATE SECTION.
    METHODS:
      get_data FOR TESTING.
ENDCLASS.


CLASS ltc_constructor_injection IMPLEMENTATION.
  METHOD get_data.
    DATA(lo_local_doc) = NEW ltd_doc( ).

    DATA(lo_cut) = NEW zcl_test_handle_docs( lo_local_doc ).

    DATA(ld_result) = lo_cut->call_selector( abap_false ).

    cl_abap_unit_assert=>assert_initial( ld_result ).
  ENDMETHOD.
ENDCLASS.

 

Wir können beim Erzeugen des CUT nun unser eigenes Test Double übergeben und kontrollieren damit das Verhalten unsere Codes beim Test.

 

Setter Injection

Die Klasse stellt eine eigene Setter Methode zur Verfügung mit deren Hilfe man die DOC ersetzen kann. Diese Methode kann dann vor der Verwendung des CUT aufgerufen werden.

CLASS zcl_test_handle_docs DEFINITION
  PUBLIC
  FINAL
  CREATE PUBLIC.

  PUBLIC SECTION.
    METHODS:
      constructor,      

      call_selector
        IMPORTING
                  id_flag          TYPE abap_bool
        RETURNING VALUE(rd_result) TYPE string,

      set_component
        IMPORTING
          io_doc TYPE REF TO zif_test_doc.
          
  PROTECTED SECTION.
  PRIVATE SECTION.
    DATA:
      mo_doc TYPE REF TO zif_test_doc.  
ENDCLASS.


CLASS zcl_test_handle_docs IMPLEMENTATION.
  METHOD constructor.
    mo_doc = NEW zcl_test_doc( ).
  ENDMETHOD.


  METHOD call_selector.
    mo_doc->do_stuff_and_select_data( id_flag ).
  ENDMETHOD.
  
  
  METHOD set_component.
    mo_doc = io_doc.
  ENDMETHOD.  
ENDCLASS.

 

Die Testklasse mit der Anwendung der Methode sieht nun wie folgt aus:

CLASS ltc_setter_injection DEFINITION FINAL FOR TESTING
  DURATION SHORT
  RISK LEVEL HARMLESS.

  PRIVATE SECTION.
    METHODS:
      get_data FOR TESTING.
ENDCLASS.


CLASS ltc_setter_injection IMPLEMENTATION.
  METHOD get_data.
    DATA(lo_local_doc) = NEW ltd_doc( ).

    DATA(lo_cut) = NEW zcl_test_handle_docs( ).
    lo_cut->set_component( lo_local_doc ).
    
    DATA(ld_result) = lo_cut->call_selector( abap_false ).

    cl_abap_unit_assert=>assert_initial( ld_result ).
  ENDMETHOD.
ENDCLASS.

 

Parameter Injection

Die Parameter Injection arbeitet mit einem optionalen Übergabeparameter beim Aufruf der Methode. Wird eine Instanz übergeben, dann wird diese verwendet, ansonsten einfach eine neue Instanz der verwendeten Klasse erzeugt.

CLASS zcl_test_handle_docs DEFINITION
  PUBLIC
  FINAL
  CREATE PUBLIC.

  PUBLIC SECTION.
    METHODS:
      constructor,      

      call_selector
        IMPORTING
                  id_flag          TYPE abap_bool
                  io_doc           TYPE REF TO zif_test_doc OPTIONAL
        RETURNING VALUE(rd_result) TYPE string.
          
  PROTECTED SECTION.
  PRIVATE SECTION.
ENDCLASS.


CLASS zcl_test_handle_docs IMPLEMENTATION.
  METHOD constructor.
  ENDMETHOD.


  METHOD call_selector.
    DATA(lo_doc) = CAST zif_test_doc( COND #(
      WHEN io_doc IS BOUND
      THEN io_doc
      ELSE NEW zcl_test_doc( )
    ) ).

    lo_doc->do_stuff_and_select_data( id_flag ).  
  ENDMETHOD.  
ENDCLASS.

 

Die Testklasse mit der Anwendung der Methode sieht nun wie folgt aus:

CLASS ltc_parameter_injection DEFINITION FINAL FOR TESTING
  DURATION SHORT
  RISK LEVEL HARMLESS.

  PRIVATE SECTION.
    METHODS:
      get_data FOR TESTING.
ENDCLASS.


CLASS ltc_parameter_injection IMPLEMENTATION.
  METHOD get_data.
    DATA(lo_local_doc) = NEW ltd_doc( ).

    DATA(lo_cut) = NEW zcl_test_handle_docs( ).

    DATA(ld_result) = lo_cut->call_selector( 
        id_flag = abap_false 
        io_doc = lo_local_doc ).

    cl_abap_unit_assert=>assert_initial( ld_result ).
  ENDMETHOD.
ENDCLASS.

 

Backdoor Injection

Bei der Backdoor Injection muss erst einmal nichts in der globalen Klasse angepasst werden. Die Backdoor wird über den FRIEND Zusatz erzeugt. Die Testklasse mit der Anwendung der Methode sieht nun wie folgt aus:

CLASS ltc_backdoor_injection DEFINITION FINAL FOR TESTING
  DURATION SHORT
  RISK LEVEL HARMLESS.

  PRIVATE SECTION.
    METHODS:
      get_data FOR TESTING.
ENDCLASS.

CLASS zcl_test_handle_docs DEFINITION LOCAL FRIENDS ltc_backdoor_injection.

CLASS ltc_backdoor_injection IMPLEMENTATION.
  METHOD get_data.
    DATA(lo_local_doc) = NEW ltd_doc( ).

    DATA(lo_cut) = NEW zcl_test_handle_docs( ).
    lo_cut->mo_doc = lo_local_doc.

    DATA(ld_result) = lo_cut->call_selector( abap_false ).

    cl_abap_unit_assert=>assert_initial( ld_result ).
  ENDMETHOD.
ENDCLASS.

 

Verwendung

Mit den vier Injection Mechanismen stehen dir einige Mittel zur Verfügung, die du während der Entwicklung einsetzen kannst. In den häufigsten Fällen werden die Constructor und die Backdoor Injection eingesetzt, da sie den geringsten Aufwand versprechen und flexibel für den Test sind. Wichtig dabei ist nur, die Erzeugung von der Verwendung der Klasse zu trennen.

Wenn du eigene Vorstellungen für den Injection Mechanismus hast, dann kannst du natürlich auch diesen verwenden. Diese bekannten Patterns sind aber am Geläufigsten.

 

Fazit

Es gibt verschiedene Möglichkeiten abhängige Komponenten zu deaktivieren, aber immer eine Möglichkeit deinen Code auch testbar zu gestalten. Denke bei der Entwicklung immer daran, dass ein anderer Entwickler vielleicht auch deine Komponenten testen oder verwenden möchte.


Enthaltene Themen:
ABAP UnitABAPUnit TestsTestbarer CodeDependency Injection
Kommentare (0)

ABAP Unit - Tipps

Kategorie - ABAP

Zum Abschluss der Serie noch ein paar Tipps die wir dir mit auf den Weg geben wollen. Hier geht es um Shortcuts und allgemeine Hinweise zu den Tests.

12.11.2021

ABAP Unit - Software-Architektur

Kategorie - ABAP

Wie könnte die Ziel Architektur in einem SAP System aussehen, wenn wir uns die eigenen Software Komponenten anschauen? Dies wollen wir in diesem Artikel klären.

05.11.2021

ABAP Unit - Testbarer Code (Teil 2)

Kategorie - ABAP

In diesem Artikel geht es um das Thema Test Isolation und wie es unseren Code testbarer macht.

22.10.2021

ABAP Unit - Testbarer Code (Teil 1)

Kategorie - ABAP

In diesem Artikel schauen wir uns an, wie du auch in älterem Code sauber neue Funktionen implementieren und du diese im Anschluss auch testen kannst.

08.10.2021

ABAP Unit - OData testen

Kategorie - ABAP

In diesem Artikel geht es um die Testbarkeit von OData Services und wie du damit auch Schnittstellen einfach und schnell testen kannst.

01.10.2021