ABAP Unit ist das integrierte Test-Framework für automatisierte Unit-Tests in ABAP. Es ermöglicht testgetriebene Entwicklung (TDD) und sichert die Codequalität durch wiederholbare, automatisierte Tests.
Grundkonzept
- Testklassen sind lokale Klassen mit dem Zusatz
FOR TESTING - Testmethoden sind Methoden mit dem Zusatz
FOR TESTING - Assertions prüfen erwartete Ergebnisse mit
CL_ABAP_UNIT_ASSERT - Tests werden über STRG+SHIFT+F10 oder das Kontextmenü ausgeführt
Syntax
CLASS ltc_test_class DEFINITION FOR TESTING DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION. METHODS: test_method FOR TESTING.ENDCLASS.
CLASS ltc_test_class IMPLEMENTATION. METHOD test_method. " Test-Logik mit Assertions cl_abap_unit_assert=>assert_equals( act = actual_value exp = expected_value ). ENDMETHOD.ENDCLASS.Beispiele
1. Einfacher Unit Test
" Zu testende KlasseCLASS lcl_calculator DEFINITION. PUBLIC SECTION. METHODS: add IMPORTING iv_a TYPE i iv_b TYPE i RETURNING VALUE(rv_result) TYPE i.ENDCLASS.
CLASS lcl_calculator IMPLEMENTATION. METHOD add. rv_result = iv_a + iv_b. ENDMETHOD.ENDCLASS.
" TestklasseCLASS ltc_calculator DEFINITION FOR TESTING DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION. DATA: mo_cut TYPE REF TO lcl_calculator. " CUT = Class Under Test
METHODS: setup, test_add_positive_numbers FOR TESTING, test_add_negative_numbers FOR TESTING, test_add_zero FOR TESTING.ENDCLASS.
CLASS ltc_calculator IMPLEMENTATION. METHOD setup. " Wird vor jedem Test ausgeführt mo_cut = NEW #( ). ENDMETHOD.
METHOD test_add_positive_numbers. DATA(lv_result) = mo_cut->add( iv_a = 5 iv_b = 3 ).
cl_abap_unit_assert=>assert_equals( act = lv_result exp = 8 msg = 'Addition positiver Zahlen fehlgeschlagen' ). ENDMETHOD.
METHOD test_add_negative_numbers. DATA(lv_result) = mo_cut->add( iv_a = -5 iv_b = -3 ).
cl_abap_unit_assert=>assert_equals( act = lv_result exp = -8 ). ENDMETHOD.
METHOD test_add_zero. DATA(lv_result) = mo_cut->add( iv_a = 10 iv_b = 0 ).
cl_abap_unit_assert=>assert_equals( act = lv_result exp = 10 ). ENDMETHOD.ENDCLASS.2. Wichtige Assertion-Methoden
CLASS ltc_assertions DEFINITION FOR TESTING DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION. METHODS: test_assert_equals FOR TESTING, test_assert_true_false FOR TESTING, test_assert_initial FOR TESTING, test_assert_bound FOR TESTING, test_assert_differs FOR TESTING, test_assert_char_cp FOR TESTING, test_fail FOR TESTING.ENDCLASS.
CLASS ltc_assertions IMPLEMENTATION. METHOD test_assert_equals. " Werte vergleichen cl_abap_unit_assert=>assert_equals( act = 42 exp = 42 msg = 'Werte sind nicht gleich' ).
" Strukturen vergleichen DATA: ls_act TYPE ty_person, ls_exp TYPE ty_person. ls_act = VALUE #( name = 'Max' age = 30 ). ls_exp = VALUE #( name = 'Max' age = 30 ).
cl_abap_unit_assert=>assert_equals( act = ls_act exp = ls_exp ).
" Tabellen vergleichen DATA: lt_act TYPE TABLE OF string, lt_exp TYPE TABLE OF string. lt_act = VALUE #( ( `A` ) ( `B` ) ). lt_exp = VALUE #( ( `A` ) ( `B` ) ).
cl_abap_unit_assert=>assert_equals( act = lt_act exp = lt_exp ). ENDMETHOD.
METHOD test_assert_true_false. DATA: lv_flag TYPE abap_bool VALUE abap_true.
" Prüfen, ob wahr cl_abap_unit_assert=>assert_true( act = lv_flag msg = 'Flag sollte true sein' ).
" Prüfen, ob falsch cl_abap_unit_assert=>assert_false( act = xsdbool( 1 = 2 ) msg = 'Ausdruck sollte false sein' ). ENDMETHOD.
METHOD test_assert_initial. DATA: lv_empty TYPE string, lv_filled TYPE string VALUE 'Test'.
" Prüfen, ob initial (leer) cl_abap_unit_assert=>assert_initial( act = lv_empty msg = 'Variable sollte initial sein' ).
" Prüfen, ob NICHT initial cl_abap_unit_assert=>assert_not_initial( act = lv_filled msg = 'Variable sollte nicht initial sein' ). ENDMETHOD.
METHOD test_assert_bound. DATA: lo_object TYPE REF TO lcl_calculator, lo_null TYPE REF TO lcl_calculator.
lo_object = NEW #( ).
" Prüfen, ob Referenz gebunden cl_abap_unit_assert=>assert_bound( act = lo_object msg = 'Objektreferenz sollte gebunden sein' ).
" Prüfen, ob NICHT gebunden cl_abap_unit_assert=>assert_not_bound( act = lo_null msg = 'Objektreferenz sollte NULL sein' ). ENDMETHOD.
METHOD test_assert_differs. " Prüfen, dass Werte unterschiedlich sind cl_abap_unit_assert=>assert_differs( act = 'ABC' exp = 'XYZ' msg = 'Werte sollten unterschiedlich sein' ). ENDMETHOD.
METHOD test_assert_char_cp. " Pattern-Matching (wie CP in IF) cl_abap_unit_assert=>assert_char_cp( act = 'Hello World' exp = 'Hello*' msg = 'String sollte mit Hello beginnen' ). ENDMETHOD.
METHOD test_fail. " Test absichtlich fehlschlagen lassen IF 1 = 2. cl_abap_unit_assert=>fail( msg = 'Dieser Code sollte nie erreicht werden' ). ENDIF. ENDMETHOD.ENDCLASS.3. Setup und Teardown
CLASS ltc_lifecycle DEFINITION FOR TESTING DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION. CLASS-DATA: gv_class_setup_done TYPE abap_bool. DATA: mv_setup_done TYPE abap_bool.
CLASS-METHODS: class_setup, " Einmal vor allen Tests class_teardown. " Einmal nach allen Tests
METHODS: setup, " Vor jedem Test teardown, " Nach jedem Test test_first FOR TESTING, test_second FOR TESTING.ENDCLASS.
CLASS ltc_lifecycle IMPLEMENTATION. METHOD class_setup. " Einmalige Initialisierung für alle Tests " z.B. Testdaten in DB anlegen gv_class_setup_done = abap_true. ENDMETHOD.
METHOD class_teardown. " Aufräumen nach allen Tests " z.B. Testdaten aus DB löschen ENDMETHOD.
METHOD setup. " Vor JEDEM Testfall " z.B. Objekt neu instanziieren mv_setup_done = abap_true. ENDMETHOD.
METHOD teardown. " Nach JEDEM Testfall " z.B. Variablen zurücksetzen CLEAR mv_setup_done. ENDMETHOD.
METHOD test_first. cl_abap_unit_assert=>assert_true( gv_class_setup_done ). cl_abap_unit_assert=>assert_true( mv_setup_done ). ENDMETHOD.
METHOD test_second. cl_abap_unit_assert=>assert_true( gv_class_setup_done ). cl_abap_unit_assert=>assert_true( mv_setup_done ). ENDMETHOD.ENDCLASS.4. Exception-Tests
CLASS lcl_validator DEFINITION. PUBLIC SECTION. METHODS: validate_age IMPORTING iv_age TYPE i RAISING cx_parameter_invalid.ENDCLASS.
CLASS lcl_validator IMPLEMENTATION. METHOD validate_age. IF iv_age < 0. RAISE EXCEPTION TYPE cx_parameter_invalid EXPORTING parameter = 'AGE'. ENDIF. ENDMETHOD.ENDCLASS.
CLASS ltc_validator DEFINITION FOR TESTING DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION. DATA: mo_cut TYPE REF TO lcl_validator.
METHODS: setup, test_valid_age FOR TESTING, test_negative_age_raises FOR TESTING.ENDCLASS.
CLASS ltc_validator IMPLEMENTATION. METHOD setup. mo_cut = NEW #( ). ENDMETHOD.
METHOD test_valid_age. " Sollte keine Exception werfen TRY. mo_cut->validate_age( 25 ). CATCH cx_parameter_invalid. cl_abap_unit_assert=>fail( 'Unerwartete Exception' ). ENDTRY. ENDMETHOD.
METHOD test_negative_age_raises. " Exception wird erwartet TRY. mo_cut->validate_age( -5 ). cl_abap_unit_assert=>fail( 'Exception erwartet' ). CATCH cx_parameter_invalid INTO DATA(lx_error). " Erwartete Exception - Test erfolgreich cl_abap_unit_assert=>assert_equals( act = lx_error->parameter exp = 'AGE' ). ENDTRY. ENDMETHOD.ENDCLASS.5. Test Doubles (Mocking)
" Interface für Dependency InjectionINTERFACE lif_database. METHODS: get_customer IMPORTING iv_id TYPE i RETURNING VALUE(rs_customer) TYPE ty_customer.ENDINTERFACE.
" Produktive ImplementierungCLASS lcl_database DEFINITION. PUBLIC SECTION. INTERFACES: lif_database.ENDCLASS.
" Zu testende KlasseCLASS lcl_customer_service DEFINITION. PUBLIC SECTION. METHODS: constructor IMPORTING io_db TYPE REF TO lif_database. METHODS: get_customer_name IMPORTING iv_id TYPE i RETURNING VALUE(rv_name) TYPE string. PRIVATE SECTION. DATA: mo_db TYPE REF TO lif_database.ENDCLASS.
CLASS lcl_customer_service IMPLEMENTATION. METHOD constructor. mo_db = io_db. ENDMETHOD.
METHOD get_customer_name. DATA(ls_customer) = mo_db->get_customer( iv_id ). rv_name = ls_customer-name. ENDMETHOD.ENDCLASS.
" Test Double (Mock)CLASS ltd_database DEFINITION FOR TESTING. PUBLIC SECTION. INTERFACES: lif_database. DATA: ms_mock_customer TYPE ty_customer.ENDCLASS.
CLASS ltd_database IMPLEMENTATION. METHOD lif_database~get_customer. " Gibt Mock-Daten zurück statt DB-Zugriff rs_customer = ms_mock_customer. ENDMETHOD.ENDCLASS.
" Testklasse mit MockCLASS ltc_customer_service DEFINITION FOR TESTING DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION. DATA: mo_cut TYPE REF TO lcl_customer_service, mo_mock_db TYPE REF TO ltd_database.
METHODS: setup, test_get_customer_name FOR TESTING.ENDCLASS.
CLASS ltc_customer_service IMPLEMENTATION. METHOD setup. " Mock erstellen mo_mock_db = NEW #( ). mo_mock_db->ms_mock_customer = VALUE #( id = 1 name = 'Test Kunde' ).
" CUT mit Mock injizieren mo_cut = NEW #( io_db = mo_mock_db ). ENDMETHOD.
METHOD test_get_customer_name. DATA(lv_name) = mo_cut->get_customer_name( 1 ).
cl_abap_unit_assert=>assert_equals( act = lv_name exp = 'Test Kunde' ). ENDMETHOD.ENDCLASS.6. Test-Attribute
CLASS ltc_attributes DEFINITION FOR TESTING DURATION MEDIUM " SHORT | MEDIUM | LONG RISK LEVEL DANGEROUS. " HARMLESS | DANGEROUS | CRITICAL
PRIVATE SECTION. METHODS: " Kategorisierung test_quick FOR TESTING,
" Test überspringen test_not_yet_implemented FOR TESTING.ENDCLASS.
CLASS ltc_attributes IMPLEMENTATION. METHOD test_quick. " Normaler Test cl_abap_unit_assert=>assert_true( abap_true ). ENDMETHOD.
METHOD test_not_yet_implemented. " Test als "noch nicht implementiert" markieren cl_abap_unit_assert=>skip( 'Noch nicht implementiert' ). ENDMETHOD.ENDCLASS.7. Testdaten mit Helper-Methoden
CLASS ltc_with_helpers DEFINITION FOR TESTING DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION. METHODS: test_process_orders FOR TESTING, " Helper-Methoden (nicht FOR TESTING) given_orders RETURNING VALUE(rt_orders) TYPE ty_orders, given_customer RETURNING VALUE(rs_customer) TYPE ty_customer.ENDCLASS.
CLASS ltc_with_helpers IMPLEMENTATION. METHOD test_process_orders. " Given DATA(lt_orders) = given_orders( ). DATA(ls_customer) = given_customer( ).
" When DATA(lv_result) = process( lt_orders ).
" Then cl_abap_unit_assert=>assert_equals( act = lv_result exp = 'OK' ). ENDMETHOD.
METHOD given_orders. rt_orders = VALUE #( ( id = 1 amount = 100 ) ( id = 2 amount = 200 ) ). ENDMETHOD.
METHOD given_customer. rs_customer = VALUE #( id = 1 name = 'Test' ). ENDMETHOD.ENDCLASS.8. Tests für private Methoden
" Zu testende KlasseCLASS lcl_processor DEFINITION FRIENDS ltc_processor. " Testklasse als Friend deklarieren
PUBLIC SECTION. METHODS: process RETURNING VALUE(rv_result) TYPE string.
PRIVATE SECTION. METHODS: calculate_internal RETURNING VALUE(rv_value) TYPE i.ENDCLASS.
" Testklasse kann auf private Methoden zugreifenCLASS ltc_processor DEFINITION FOR TESTING DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION. DATA: mo_cut TYPE REF TO lcl_processor.
METHODS: setup, test_calculate_internal FOR TESTING.ENDCLASS.
CLASS ltc_processor IMPLEMENTATION. METHOD setup. mo_cut = NEW #( ). ENDMETHOD.
METHOD test_calculate_internal. " Direkter Zugriff auf private Methode durch FRIENDS DATA(lv_value) = mo_cut->calculate_internal( ).
cl_abap_unit_assert=>assert_equals( act = lv_value exp = 42 ). ENDMETHOD.ENDCLASS.9. SQL Test Double Framework
CLASS ltc_with_sql_double DEFINITION FOR TESTING DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION. CLASS-DATA: mo_environment TYPE REF TO if_osql_test_environment.
CLASS-METHODS: class_setup, class_teardown.
METHODS: setup, test_read_customer FOR TESTING.ENDCLASS.
CLASS ltc_with_sql_double IMPLEMENTATION. METHOD class_setup. " Test-Environment für DB-Tabellen erstellen mo_environment = cl_osql_test_environment=>create( i_dependency_list = VALUE #( ( 'KNA1' ) ) ). ENDMETHOD.
METHOD class_teardown. mo_environment->destroy( ). ENDMETHOD.
METHOD setup. " Testdaten einfügen DATA: lt_kna1 TYPE TABLE OF kna1.
lt_kna1 = VALUE #( ( mandt = sy-mandt kunnr = '0000001000' name1 = 'Test Kunde' ) ).
mo_environment->insert_test_data( lt_kna1 ). ENDMETHOD.
METHOD test_read_customer. " SELECT liest jetzt aus Test Double statt echter DB SELECT SINGLE name1 FROM kna1 WHERE kunnr = '0000001000' INTO @DATA(lv_name).
cl_abap_unit_assert=>assert_equals( act = lv_name exp = 'Test Kunde' ). ENDMETHOD.ENDCLASS.Test-Ausführung
" In Eclipse/ADT:STRG + SHIFT + F10 " Alle Tests ausführenRechtsklick → Run As → ABAP Unit Test
" In SE80:Rechtsklick → Unit Test ausführenDURATION und RISK LEVEL
| DURATION | Erwartete Laufzeit |
|---|---|
| SHORT | < 1 Sekunde |
| MEDIUM | < 5 Sekunden |
| LONG | > 5 Sekunden |
| RISK LEVEL | Beschreibung |
|---|---|
| HARMLESS | Keine Datenbankänderungen |
| DANGEROUS | Mögliche Testdaten-Änderungen |
| CRITICAL | Produktionsdaten könnten betroffen sein |
Wichtige Hinweise / Best Practice
- Testklassen in lokalen Klassen (Definition/Implementation am Ende der Klasse).
- Naming:
ltc_für Testklassen,ltd_für Test Doubles,test_für Methoden. - AAA-Pattern: Arrange (Given), Act (When), Assert (Then).
- Ein Assert pro Test - fokussierte Tests sind besser wartbar.
- Setup/Teardown für wiederholte Initialisierung/Aufräumen nutzen.
- Dependency Injection für testbare Architektur mit Mocks.
- FRIENDS ermöglicht Tests privater Methoden (sparsam verwenden).
cl_abap_unit_assert=>skip()für noch nicht implementierte Tests.- SQL Test Double Framework für Datenbank-unabhängige Tests.
- Tests regelmäßig ausführen - idealerweise in CI/CD-Pipeline.