ABAP Unit - Altobjekte
In diesem Artikel geht es um sogenannten Legacy Code und wie du auch dort ABAP Unit verwenden kannst. Empfehlen können wir diese Art aber nur eingeschränkt.
Inhaltsverzeichnis
Der Begriff Altobjekt klingt etwas abwertend, wenn es um Reports und Funktionsbausteine geht, doch hier geht es auch um die Möglichkeiten, die man beim Test hat. Mehr dazu in diesem Artikel.
Report
Du hast noch einen alten Report im System der oft von dir überarbeitet wird und du deshalb ein paar Tests implementieren willst, damit zukünftige Entwicklungen stabil implementiert werden können? Eine Testklasse in einem Report zu implementieren ist kein Problem, dabei solltest du allerdings zwei wichtige Punkte beachten:
- Spaghetti-Code kann nicht getestet werden, da er keine aufrufbaren Unterroutinen zur Verfügung stellt
- Forms oder Methoden sollten mit ordentlichen Schnittstellen ausgestattet sein und auf so wenig wie möglichen globalen Daten basieren
Wenn du also bereits saubere Unterroutinen (Methoden oder Forms) implementiert hast, ist es kein Problem eine Testklasse zu implementieren. Dazu zwei Beispiele eines Reports die inhaltlich das Gleiche machen. Es werden Daten zum Buchungskreis gelesen, in eine Tabelle aufgenommen und diese Dann per CL_DEMO_OUTPUT ausgegeben.
Das erste Beispiel mit einer Umsetzung als lokale Klasse und einer Testklasse:
REPORT ztest_report_with_class.
*** Selection screen
PARAMETERS:
p_bukrs TYPE t001-bukrs.
*** Report logic
CLASS lcl_prog DEFINITION FINAL.
PUBLIC SECTION.
METHODS:
main,
select_company_code
IMPORTING
id_bukrs TYPE bukrs
RETURNING VALUE(rs_company_data) TYPE t001.
ENDCLASS.
CLASS lcl_prog IMPLEMENTATION.
METHOD main.
DATA:
lt_company_data TYPE STANDARD TABLE OF t001.
INSERT select_company_code( p_bukrs ) INTO TABLE lt_company_data.
cl_demo_output=>display_data( lt_company_data ).
ENDMETHOD.
METHOD select_company_code.
SELECT SINGLE *
FROM t001
WHERE bukrs = @id_bukrs
INTO @rs_company_data.
ENDMETHOD.
ENDCLASS.
*** Local testclass
CLASS ltc_prog DEFINITION FINAL FOR TESTING
DURATION SHORT
RISK LEVEL HARMLESS.
PRIVATE SECTION.
METHODS:
select_existing_company_code FOR TESTING RAISING cx_static_check.
ENDCLASS.
CLASS ltc_prog IMPLEMENTATION.
METHOD select_existing_company_code.
DATA(lo_cut) = NEW lcl_prog( ).
DATA(ls_found) = lo_cut->select_company_code( '4711' ).
cl_abap_unit_assert=>assert_not_initial( ls_found ).
ENDMETHOD.
ENDCLASS.
*** Report
START-OF-SELECTION.
NEW lcl_prog( )->main( ).
Das zweite Beispiel noch mit klassischen FORM Routinen und einer Testklasse:
REPORT ztest_report_with_forms.
*** Selection screen
PARAMETERS:
p_bukrs TYPE t001-bukrs.
*** Report
START-OF-SELECTION.
PERFORM main.
*** Report logic
FORM main.
DATA:
lt_company_data TYPE STANDARD TABLE OF t001,
ls_company_data TYPE t001.
PERFORM select_company_code
USING p_bukrs
CHANGING ls_company_data.
INSERT ls_company_data INTO TABLE lt_company_data.
cl_demo_output=>display_data( lt_company_data ).
ENDFORM.
FORM select_company_code USING p_bukrs TYPE t001-bukrs
CHANGING cs_company_code TYPE t001.
SELECT SINGLE *
FROM t001
WHERE bukrs = @p_bukrs
INTO @cs_company_code.
ENDFORM.
*** Local testclass
CLASS ltc_prog DEFINITION FINAL FOR TESTING
DURATION SHORT
RISK LEVEL HARMLESS.
PRIVATE SECTION.
METHODS:
select_existing_company_code FOR TESTING RAISING cx_static_check.
ENDCLASS.
CLASS ltc_prog IMPLEMENTATION.
METHOD select_existing_company_code.
DATA:
ls_found TYPE t001.
PERFORM select_company_code
USING '4711'
CHANGING ls_found.
cl_abap_unit_assert=>assert_not_initial( ls_found ).
ENDMETHOD.
ENDCLASS.
Funktionsbaustein
Funktionsbausteine können von dir genauso getestet werden und eignen sich fast sogar besser dazu als ein Report. Das liegt an der Kapselung der Daten und der Schnittstelle, die ein Funktionsbaustein von vornherein mit sich bringt. Wenn du in das Rahmenprogramm einer Funktionsgruppe schaust, findest du bereits viele auskommentierte Includes, die für verschiedene Dinge vorgesehen sind.
Das Include mit der Endung T99 kann für Tests der Funktionsbausteine der Funktionsgruppe genutzt werden. Dafür legen wir einen einfachen Baustein im System an, der für uns eine einfache Berechnung durchführen soll.
FUNCTION z_bs_demo_calculate_two_number
IMPORTING
VALUE(id_number_one) TYPE i
VALUE(id_number_two) TYPE i
EXPORTING
VALUE(ed_result) TYPE i.
ed_result = id_number_one + id_number_two.
ENDFUNCTION.
Im Anschluss legen wir das Include an und implementieren die Testklasse, die dann den Funktionsbaustein aufruft. In diesem Fall haben wir als Beispiel einmal nur eine Testmethode angelegt.
CLASS ltc_function_modules DEFINITION FINAL FOR TESTING
DURATION SHORT
RISK LEVEL HARMLESS.
PRIVATE SECTION.
METHODS:
calculate_1_and_5 FOR TESTING.
ENDCLASS.
CLASS ltc_function_modules IMPLEMENTATION.
METHOD calculate_1_and_5.
DATA:
ld_result TYPE i.
CALL FUNCTION 'Z_BS_DEMO_CALCULATE_TWO_NUMBER'
EXPORTING
id_number_one = 1
id_number_two = 5
IMPORTING
ed_result = ld_result.
cl_abap_unit_assert=>assert_equals( act = ld_result exp = 6 ).
ENDMETHOD.
ENDCLASS.
Damit ist es einfach möglich die Funktionsbausteine, Klassen und Forms innerhalb einer Funktionsgruppe ebenfalls mit Tests auszustatten und bei jeder Änderung mit zu testen.
Einschränkungen
Dabei gibt es für “Altobjekte” noch kleine Einschränkung, denn es können nicht alle Techniken angewandt werden, die wir in diesem Buch behandeln. So sind zum Beispiel keine SEAMs verfügbar.
Ein kleiner Tipp von unserer Seite für dich ist deshalb, am besten alle Entwicklungen in globalen Klassen durchführen und Funktionsbausteine, sowie Reports nur als Hüllen für den Aufruf verwenden, damit ist der Großteil der Logik sauber im OO verschalt und du kannst die volle Effizienz mit ABAP Unit ausschöpfen. Mehr dazu werden wir dir noch im Architektur Artikel dieser Serie erzählen.
Fazit
Auch für Altobjekte oder Legacy Code kannst du ABAP Unit Tests schreiben, dafür muss der Code allerdings einige Voraussetzungen erfüllen, damit dies auch möglich ist. Doch je mehr Unit Tests du im System hast und damit eine hohe Abdeckung, desto eher fallen Probleme bei Anpassungen auf.