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

ABAP Unit - TDF (Function Double)

219

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

Werbung


Der letzte Artikel zum Thema Unit Tests ist bereits eine ganze Weile her. Damals haben wir die Serie zum Thema automatisches Testen eigentlich schon abgeschlossen, doch in der Zwischenzeit hat SAP weitere Features zur Verfügung gestellt. Dabei gibt es nun einen Test-Double für Funktionsbausteine, den wir uns in diesem Artikel einmal näher anschauen wollen.

 

Einleitung

Ein Test-Double ist dafür da, Abhängigkeiten zu aufgerufenen Objekten zu eliminieren. Das heißt, immer wenn eine Datenbank angefragt, ein Service aufgerufen, eine andere Klasse gestartet oder ein Funktionsbaustein implementiert wird, erhält man Abhängigkeiten. Da man in einem Unit Test nur seinen eigenen Code prüfen möchte, sollten solche Abhängigkeiten zur Testlaufzeit verschwinden. Dafür gibt es das Konzept des Doubles, es wird die Abhängigkeit ausgetauscht und man erhält die Kontrolle über die Rückgabewerte. Tests sind wieder stabil und wiederholbar.

 

Vorbereitung

Dazu benötigen wir eine Abhängigkeit, im speziellen Fall einen Funktionsbaustein. Dazu legen wir uns einen Testbaustein an, der Rechenoperationen ausführt. Dabei implementieren wir nur die Operationen für die Addition und Subtraktion:

FUNCTION z_bs_demo_test_function
  IMPORTING
    VALUE(id_input_one) TYPE i
    VALUE(id_input_two) TYPE i
    VALUE(id_method) TYPE string
  EXPORTING
    VALUE(ed_result) TYPE i.

  CASE id_method.
    WHEN '+'.
      ed_result = id_input_one + id_input_two.
    WHEN '-'.
      ed_result = id_input_one - id_input_two.
  ENDCASE.
ENDFUNCTION.

 

Konfiguration

Zuerst einmal benötigen wir eine Variable auf globaler Ebene, die unser Test-Environment hält. Wie für Datenbanken und CDS Views gibt es ein eigenes Environment:

CLASS-DATA:
  go_function TYPE REF TO if_function_test_environment.

 

Zuerst einmal initialisieren wir das Environment und übergeben die Funktionsbausteine, für die wir einen Test-Double anlegen wollen.

" Initialize double
go_function = cl_function_test_environment=>create( VALUE #( ( 'Z_BS_DEMO_TEST_FUNCTION' ) ) ).

 

Als Nächstes holen wir uns das Double das wir konfigurieren wollen, dies geschieht über den Namen des Funktionsbausteins.

" Get double for function module
DATA(lo_demo_function) = go_function->get_double( 'Z_BS_DEMO_TEST_FUNCTION' ).

 

Dann erstellen wir die Eingabe-Konfiguration, dieses Set definiert einen Zustand. Wenn alle Parameter wie definiert beim Aufruf des Funktionsbausteins übergeben werden, dann wird dieses Set ausgelöst.

" Configure input configuration
DATA(lo_input_config) = lo_demo_function->create_input_configuration(
  )->set_importing_parameter( name  = 'ID_INPUT_ONE' value = 10
  )->set_importing_parameter( name  = 'ID_INPUT_TWO' value = 10
  )->set_importing_parameter( name  = 'ID_METHOD' value = '*'
).

 

Passend zur Eingabe-Konfiguration, muss nun auch eine Ausgabe-Konfiguration definiert werden. Damit werden die Ausgabeparameter des Funktionsbausteins mit Werten versorgt.

" Configure output configuration
DATA(lo_output_config) = lo_demo_function->create_output_configuration(
  )->set_exporting_parameter( name = 'ED_RESULT' value = 100
).

 

Im letzten Schritt müssen die beiden Konfigurationen nun zusammengebracht werden. Dafür konfigurieren wir den Aufruf, wenn die Eingabewerte der Vorgabe entsprechen, dann soll die Ausgabe entsprechend der Vorgabe gesetzt werden.

" Set matching configuration
lo_demo_function->configure_call( )->when( lo_input_config )->then_set_output( lo_output_config ).

 

Möglichkeiten

Neben der Möglichkeit direkt die Ausgabe zu setzen, kannst du auch anderes Verhalten simulieren, wie die Ausgabe von klassischen Ausnahmen, OO-Ausnahmen oder einer direkten Antwort. Damit verhält sich das Double auch wie der Test-Double für Klassen. Mehr Informationen erhältst du in der Schnittstelle:

 

Testfall

Definieren wir nun einen ersten abstrakten Testfall, um dieses Verhalten zu testen. Dazu rufen wir in unserem Testfall den Funktionsbaustein direkt auf. Normalerweise würden wir die globale Klasse testen und möchten eigentlich den Funktionsbaustein deaktivieren. Zur Veranschaulichung testen wir nur einmal den Funktionsbaustein, um das eigentliche Double zu testen:

DATA:
  ld_result TYPE i.

CALL FUNCTION 'Z_BS_DEMO_TEST_FUNCTION'
  EXPORTING
    id_input_one = 10
    id_input_two = 10
    id_method    = '*'
  IMPORTING
    ed_result    = ld_result.

cl_abap_unit_assert=>assert_equals( act = ld_result exp = 100 ).

 

Die weiteren Testfälle legen wir an (siehe vollständiges Beispiel), wobei ein Testfall nicht funktionieren soll. Hier legen wir für eine Kombination keine Parameter an. Damit sollte keine Konfiguration gefunden werden. Damit funktionieren auch Szenarien, die eigentlich gar nicht im Funktionsbaustein definiert wurden:

 

Vollständiges Beispiel

Wie immer noch einmal das gesamte Beispiel der globalen Testklasse mit den entsprechenden Kommentaren und Definitionen:

"! @testing Z_BS_DEMO_TEST_FUNCTION
CLASS zcl_bs_demo_funcdouble DEFINITION PUBLIC FINAL CREATE PUBLIC
  FOR TESTING
  DURATION SHORT
  RISK LEVEL HARMLESS.

  PUBLIC SECTION.
  PROTECTED SECTION.
  PRIVATE SECTION.
    CLASS-DATA:
      go_function TYPE REF TO if_function_test_environment.

    CLASS-METHODS:
      class_setup,
      class_teardown.

    METHODS:
      not_defined_method FOR TESTING,
      wrong_substraction FOR TESTING,
      not_defined_case FOR TESTING.
ENDCLASS.


CLASS zcl_bs_demo_funcdouble IMPLEMENTATION.
  METHOD class_setup.
    go_function = cl_function_test_environment=>create( VALUE #( ( 'Z_BS_DEMO_TEST_FUNCTION' ) ) ).

    DATA(lo_demo_function) = go_function->get_double( 'Z_BS_DEMO_TEST_FUNCTION' ).

    " Configure test double - First call
    DATA(lo_input_config) = lo_demo_function->create_input_configuration(
      )->set_importing_parameter( name  = 'ID_INPUT_ONE' value = 10
      )->set_importing_parameter( name  = 'ID_INPUT_TWO' value = 10
      )->set_importing_parameter( name  = 'ID_METHOD' value = '*'
    ).

    DATA(lo_output_config) = lo_demo_function->create_output_configuration(
      )->set_exporting_parameter( name = 'ED_RESULT' value = 100
    ).

    lo_demo_function->configure_call( )->when( lo_input_config )->then_set_output( lo_output_config ).

    " Configure test double - Second call
    lo_input_config = lo_demo_function->create_input_configuration(
      )->set_importing_parameter( name  = 'ID_INPUT_ONE' value = 20
      )->set_importing_parameter( name  = 'ID_INPUT_TWO' value = 10
      )->set_importing_parameter( name  = 'ID_METHOD' value = '-'
    ).

    lo_output_config = lo_demo_function->create_output_configuration(
      )->set_exporting_parameter( name = 'ED_RESULT' value = 11
    ).

    lo_demo_function->configure_call( )->when( lo_input_config )->then_set_output( lo_output_config ).
  ENDMETHOD.


  METHOD class_teardown.
    go_function->clear_doubles( ).
  ENDMETHOD.


  METHOD not_defined_method.
    DATA:
      ld_result TYPE i.

    CALL FUNCTION 'Z_BS_DEMO_TEST_FUNCTION'
      EXPORTING
        id_input_one = 10
        id_input_two = 10
        id_method    = '*'
      IMPORTING
        ed_result    = ld_result.

    cl_abap_unit_assert=>assert_equals( act = ld_result exp = 100 ).
  ENDMETHOD.


  METHOD wrong_substraction.
    DATA:
      ld_result TYPE i.

    CALL FUNCTION 'Z_BS_DEMO_TEST_FUNCTION'
      EXPORTING
        id_input_one = 20
        id_input_two = 10
        id_method    = '-'
      IMPORTING
        ed_result    = ld_result.

    cl_abap_unit_assert=>assert_equals( act = ld_result exp = 11 ).
  ENDMETHOD.


  METHOD not_defined_case.
    DATA:
      ld_result TYPE i.

    CALL FUNCTION 'Z_BS_DEMO_TEST_FUNCTION'
      EXPORTING
        id_input_one = 15
        id_input_two = 20
        id_method    = '+'
      IMPORTING
        ed_result    = ld_result.

    cl_abap_unit_assert=>assert_equals( act = ld_result exp = 35 ).
  ENDMETHOD.
ENDCLASS.

 

Fazit

Nun ist es auch auf leichtem Weg möglich, Funktionsbausteine in unseren Objekten mitzutesten und dabei auf Test-Seams zu verzichten. Funktionsbausteine werden in Zukunft zwar immer weniger eingesetzt werden, doch solange sie existieren, sollte es auch eine Möglichkeit geben, sie zu mocken.

 

Quelle:
Help Portal - ABAP Function Modules


Enthaltene Themen:
ABAP UnitABAPUnit TestsTest-Double-FrameworkFunktionsbaustein
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 - 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

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

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