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)

193

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)



Und weiter ...

Bist du zufrieden mit dem Inhalt des Artikels? Wir posten jeden 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.


ABAP Unit - Testausführung

Kategorie - ABAP

Welche Möglichkeiten gibt es zur Ausführung von ABAP Unit und welche versteckten Funktionen kennst du vielleicht noch nicht? Mehr Details zu den verschiedenen Modi in diesem Artikel.

25.06.2024

ABAP Unit - Automatisierung

Kategorie - ABAP

Wann gehen in der Entwicklung Objekte kaputt und welche Seiteneffekte können Anpassungen haben? In diesem Artikel schauen wir uns das Thema einmal näher an.

05.01.2024

ABAP Unit - TDF (Function Double)

Kategorie - ABAP

Schauen wir uns in diesem Artikel einmal an, wie wir mit Funktionsbausteinen als Abhängigkeit umgehen können, ohne diese mitzutesten.

07.04.2023

RAP - ABAP Unit (Service)

Kategorie - ABAP

Wie testet man den Service einer Schnittstelle oder RAP App eigentlich automatisch? In diesem Artikel schauen wir uns Unit Tests für OData Services an.

20.01.2023

RAP - ABAP Unit (Business Objekt)

Kategorie - ABAP

An dieser Stelle schauen wir uns einmal an, wie wir unser RAP Business Objekt automatisch testen können, um so alle wichtigen Funktionen per Knopfdruck zu prüfen.

13.01.2023