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 1)

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.

Werbung

Was bedeutet testbaren Code zu schreiben? In diesem Artikel wollen wir auf einige Methoden eingehen und wie du dein bestehendes Coding entsprechend umbauen oder erweitern kannst, damit er testbar wird.

 

Allgemein

Im Allgemeinen sprechen wir von testbarem Code, wenn dieser automatisch mit ABAP Unit Tests geprüft werden kann. Also eine saubere Schnittstelle zu einer Methode oder einem Unterprogramm existiert und so wenig wie möglich Abhängigkeiten zu globalen Daten bestehen, also einer sauberen Kapselung des Codes.

 

Island of Happiness

Methodik

Beschreibt eine Methode zur Erweiterung von Legacy Code durch eine Erweiterung von Logik oder Funktionen. Im folgenden Beispiel haben wir Legacy Code, der durch eine neue Funktionalität erweitert werden soll. In solch einem Fall kann die neue Logik als eigene Klasse entkoppelt vom Hauptcode entwickelt werden. Die entsprechend wichtigen Daten und Logiken werden über Methoden der neuen Klasse übergeben. Damit ist die neue Logik in einer globalen Klasse gekapselt und kann mit ABAP Unit Tests automatisiert getestet werden. All unsere neue Logik kann sauber entwickelt und gekapselt werden und wir müssen den bestehenden Code kaum anpassen. 

 

Beispiel

Der Report wurde klassisch ohne FORM Routinen entwickelt und enthält noch sehr viele globale Variablen und Abhängigkeiten untereinander. Unsere neue Implementierung soll den Report um neue Funktionen erweitern, bei der wir die Ausgabedaten noch um zusätzliche Geodaten anreichern wollen. Wir möchten dabei Unit Tests für die neue Funktionalität zur Verfügung stellen.

REPORT z_test_ioh.

*----------------------------------------------------------------------*
*--- Globale data
*----------------------------------------------------------------------*
TABLES:
  t001.

TYPES:
  BEGIN OF ts_output,
    bukrs      TYPE t001-bukrs,
    butxt      TYPE t001-butxt,
    name1      TYPE adrc-name1,
    name2      TYPE adrc-name2,
    city1      TYPE adrc-city1,
    addr_group TYPE adrc-addr_group,
  END OF ts_output,
  tt_output TYPE STANDARD TABLE OF ts_output WITH EMPTY KEY.

DATA:
  gt_company_code TYPE SORTED TABLE OF t001 WITH UNIQUE KEY bukrs,
  gs_company_code TYPE t001,
  gs_address      TYPE adrc,
  gt_output_table TYPE tt_output,
  gs_output_table TYPE ts_output,
  gs_vari         TYPE disvariant,
  go_alv          TYPE REF TO cl_salv_table.

*----------------------------------------------------------------------*
*--- Selection screen
*----------------------------------------------------------------------*
SELECTION-SCREEN BEGIN OF BLOCK b01.
  SELECT-OPTIONS:
    s_bukrs FOR t001-bukrs,
    s_waers FOR t001-waers.
  PARAMETERS:
    p_vari TYPE slis_vari DEFAULT '/DEFAULT',
    p_test AS CHECKBOX DEFAULT abap_true.
SELECTION-SCREEN END OF BLOCK b01.

*----------------------------------------------------------------------*
*--- Events
*----------------------------------------------------------------------*
INITIALIZATION.
  AUTHORITY-CHECK OBJECT 'S_TCODE'
   ID 'TCD' FIELD 'Z60DUMMY_TCODE'.
  IF sy-subrc <> 0.
    MESSAGE e000(z60bc) WITH 'No authority for transaction' 'Z60DUMMY_TCODE'.
  ENDIF.


AT SELECTION-SCREEN ON VALUE-REQUEST FOR p_vari.
  gs_vari-report = sy-repid.
  gs_vari-username = sy-uname.
  gs_vari-handle = 'HDL'.

  CALL FUNCTION 'REUSE_ALV_VARIANT_F4'
    EXPORTING
      is_variant = gs_vari
      i_save     = 'A'
    IMPORTING
      es_variant = gs_vari
    EXCEPTIONS
      OTHERS     = 1.

  IF sy-subrc = 0.
    p_vari = gs_vari-variant.
  ENDIF.


START-OF-SELECTION.
  CLEAR: gt_company_code, gt_output_table.

  SELECT *
    FROM t001
    WHERE bukrs IN @s_bukrs
      AND waers IN @s_waers
    INTO TABLE @gt_company_code.

  LOOP AT gt_company_code INTO gs_company_code.
    CLEAR: gs_output_table.
    gs_output_table = CORRESPONDING #( gs_company_code ).

    SELECT SINGLE *
      FROM adrc
      WHERE addrnumber = @gs_company_code-adrnr
      INTO @gs_address.

    IF sy-subrc = 0.
      gs_output_table = CORRESPONDING #( BASE ( gs_output_table ) gs_address ).
    ENDIF.

    INSERT gs_output_table INTO TABLE gt_output_table.
  ENDLOOP.

  cl_salv_table=>factory(
   IMPORTING r_salv_table = go_alv
   CHANGING t_table = gt_output_table ).

  DATA(lo_func) = go_alv->get_functions( ).
  lo_func->set_all( ).

  DATA(lo_disp) = go_alv->get_display_settings( ).
  lo_disp->set_striped_pattern( abap_true ).

  lo_disp->set_list_header( 'Found data' ).

  DATA(lo_lay) = go_alv->get_layout( ).
  lo_lay->set_default( abap_true ).
  lo_lay->set_key( VALUE #( report = sy-repid handle = 'HDL' ) ).
  lo_lay->set_save_restriction( ).

  IF p_vari IS NOT INITIAL.
    lo_lay->set_initial_layout( p_vari ).
  ENDIF.

  go_alv->display( ).

 

Die neue Funktion implementieren wir in einer globalen Klasse. Entsprechend stellen wir ein Interface zur Verfügung, welches die Schnittstelle zum Report bereitstellt. Weiterhin benötigen wir für die Schnittstelle zu den Methoden die Ausgabestruktur, die wir dann entsprechend bekannt geben müssen. Die Struktur übernehmen wir mit ins Interface und ersetzen die Definition im Report. Entsprechend sieht unsere Schnittstelle dann wie folgt aus:

INTERFACE zif_test_ioh PUBLIC.
  TYPES:
    td_geometry_data TYPE p LENGTH 15 DECIMALS 8,

    BEGIN OF ts_output,
      bukrs      TYPE t001-bukrs,
      butxt      TYPE t001-butxt,
      name1      TYPE adrc-name1,
      name2      TYPE adrc-name2,
      city1      TYPE adrc-city1,
      addr_group TYPE adrc-addr_group,
      " New Fields
      latitude   TYPE td_geometry_data,
      longitude  TYPE td_geometry_data,
    END OF ts_output,
    tt_output TYPE STANDARD TABLE OF ts_output WITH EMPTY KEY,

    BEGIN OF ts_geometry,
      latitude  TYPE td_geometry_data,
      longitude TYPE td_geometry_data,
    END OF ts_geometry.

  METHODS:
    enrich_data
      CHANGING
        cs_output TYPE ts_output.
ENDINTERFACE.

 

Der Report wird nun an den entsprechenden Stellen mit unserer neuen Logik angereichert, um so mit minimalen Änderungen zu arbeiten.

  • Änderung des Deklarationsteils

 

  • Instanziierung des Objekts

 

  • Aufruf der Zusatzlogik

 

Am Ende implementieren wir noch die Logik in der Klasse und ABAP Unit Tests, um unseren geschriebenen Code automatisiert testbar zu machen und zukünftige Erweiterungen einfacher durchführen zu können.

 

Abschluss

Die Beispielimplementierung der Klasse findest du hier an dieser Stelle, damit kannst du die neuen Funktionen sauber kapseln:

CLASS zcl_test_ioh DEFINITION PUBLIC FINAL CREATE PUBLIC.
  PUBLIC SECTION.
    INTERFACES zif_test_ioh.

  PROTECTED SECTION.
  PRIVATE SECTION.
    METHODS:
      get_geolocation_for_city
        IMPORTING
                  id_city            TYPE adrc-city1
        RETURNING VALUE(rs_geometry) TYPE zif_60bs_test_ioh=>ts_geometry.
ENDCLASS.

CLASS zcl_test_ioh IMPLEMENTATION.
  METHOD zif_60bs_test_ioh~enrich_data.
    DATA(ls_geometry) = get_geolocation_for_city( cs_output-city1 ).
    IF ls_geometry IS INITIAL.
      RETURN.
    ENDIF.

    cs_output-latitude = ls_geometry-latitude.
    cs_output-longitude = ls_geometry-longitude.
  ENDMETHOD.


  METHOD get_geolocation_for_city.
    " Call geolocation api
  ENDMETHOD.
ENDCLASS.

 

Fazit

An unserem Beispiel erkennst du, dass selbst in altem Code sauber neuer Code implementiert werden kann. Im Anschluss kannst du diese neuen Funktionen noch sauber mit ABAP Unit testen und validieren.


Enthaltene Themen:
ABAP UnitABAPUnit TestsTestbarer CodeIsland of Hapiness
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 3)

Kategorie - ABAP

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

29.10.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 - 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