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)

271

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)



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