Unit Testing ist in ABAP Cloud wichtiger denn je. Dieser Guide zeigt dir, wie du produktionsreifen, getesteten Code schreibst.
🎯 Warum Unit Testing in ABAP Cloud?
Vorteile
- ✅ Frühe Fehlererkennung (vor Produktion)
- ✅ Refactoring-Sicherheit (Änderungen ohne Angst)
- ✅ Dokumentation (Tests = Spezifikation)
- ✅ CI/CD-Ready (automatisierte Tests)
- ✅ Code-Qualität (erzwingt testbaren Code)
ABAP Cloud Besonderheiten
- Kein Debugger in Produktion → Tests unverzichtbar
- RAP Framework → spezielle Test-Tools
- CDS Views → CDS Test Environment
1. ABAP Unit Basics
Erste Test-Klasse
CLASS zcl_calculator DEFINITION PUBLIC. PUBLIC SECTION. CLASS-METHODS add IMPORTING iv_a TYPE i iv_b TYPE i RETURNING VALUE(rv_result) TYPE i.ENDCLASS.
CLASS zcl_calculator IMPLEMENTATION. METHOD add. rv_result = iv_a + iv_b. ENDMETHOD.ENDCLASS.
" ===== TEST-KLASSE =====CLASS ltc_calculator_test DEFINITION FINAL FOR TESTING DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION. METHODS test_add_positive FOR TESTING. METHODS test_add_negative FOR TESTING. METHODS test_add_zero FOR TESTING.ENDCLASS.
CLASS ltc_calculator_test IMPLEMENTATION. METHOD test_add_positive. " Given DATA(lv_a) = 5. DATA(lv_b) = 3.
" When DATA(lv_result) = zcl_calculator=>add( iv_a = lv_a iv_b = lv_b ).
" Then cl_abap_unit_assert=>assert_equals( act = lv_result exp = 8 msg = '5 + 3 should equal 8' ). ENDMETHOD.
METHOD test_add_negative. DATA(lv_result) = zcl_calculator=>add( iv_a = -5 iv_b = 3 ).
cl_abap_unit_assert=>assert_equals( act = lv_result exp = -2 ). ENDMETHOD.
METHOD test_add_zero. DATA(lv_result) = zcl_calculator=>add( iv_a = 0 iv_b = 0 ).
cl_abap_unit_assert=>assert_equals( act = lv_result exp = 0 ). ENDMETHOD.ENDCLASS.Tests ausführen
In ADT:
- Rechtsklick auf Klasse →
Run As→ABAP Unit Test - Ergebnisse im
ABAP UnitTab
Alle Tests grün? ✅ Perfekt!
2. Test-Struktur: Given-When-Then
Best Practice Pattern:
METHOD test_something. " GIVEN (Arrange) " - Setup Testdaten " - Preconditions erstellen
DATA(lv_input) = 'Test'. DATA(lo_object) = NEW zcl_my_class( ).
" WHEN (Act) " - Methode ausführen die getestet wird
DATA(lv_result) = lo_object->process( lv_input ).
" THEN (Assert) " - Ergebnis prüfen
cl_abap_unit_assert=>assert_equals( act = lv_result exp = 'EXPECTED' ).ENDMETHOD.3. Assertion-Methoden
Gleichheit prüfen
" Wert-Gleichheitcl_abap_unit_assert=>assert_equals( act = lv_actual exp = lv_expected msg = 'Values should match').
" Objekt-Gleichheitcl_abap_unit_assert=>assert_bound( act = lo_object msg = 'Object should be instantiated').
" Nicht-Gleichheitcl_abap_unit_assert=>assert_differs( act = lv_actual exp = lv_wrong_value).Boole’sche Prüfungen
" True/Falsecl_abap_unit_assert=>assert_true( act = lv_condition msg = 'Should be true').
cl_abap_unit_assert=>assert_false( act = lv_condition).Initial/Not Initial
cl_abap_unit_assert=>assert_initial( act = lv_value msg = 'Should be initial').
cl_abap_unit_assert=>assert_not_initial( act = lv_value).Tabellen-Prüfungen
" Tabellengrößecl_abap_unit_assert=>assert_table_contains( table = lt_result line = ls_expected_line).
" Anzahl ZeilenDATA(lv_lines) = lines( lt_result ).cl_abap_unit_assert=>assert_equals( act = lv_lines exp = 3).Exceptions prüfen
METHOD test_exception_thrown. DATA(lo_object) = NEW zcl_my_class( ).
TRY. lo_object->method_that_throws( 'invalid' ).
" Wenn wir hier ankommen: Test failed! cl_abap_unit_assert=>fail( msg = 'Exception should have been thrown' ).
CATCH zcx_my_exception INTO DATA(lx_error). " Erwartete Exception → Test passed cl_abap_unit_assert=>assert_bound( lx_error ). ENDTRY.ENDMETHOD.4. Test Fixtures (Setup & Teardown)
CLASS ltc_test DEFINITION FOR TESTING DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION. " Class-Level (einmal für alle Tests) CLASS-DATA lo_shared_object TYPE REF TO zcl_my_class.
CLASS-METHODS class_setup. CLASS-METHODS class_teardown.
" Instance-Level (vor/nach jedem Test) DATA lo_test_object TYPE REF TO zcl_my_class.
METHODS setup. METHODS teardown.
METHODS test_1 FOR TESTING. METHODS test_2 FOR TESTING.ENDCLASS.
CLASS ltc_test IMPLEMENTATION. METHOD class_setup. " Wird 1× vor allen Tests ausgeführt lo_shared_object = NEW zcl_my_class( ). ENDMETHOD.
METHOD class_teardown. " Wird 1× nach allen Tests ausgeführt FREE lo_shared_object. ENDMETHOD.
METHOD setup. " Wird vor JEDEM Test ausgeführt lo_test_object = NEW zcl_my_class( ). ENDMETHOD.
METHOD teardown. " Wird nach JEDEM Test ausgeführt CLEAR lo_test_object. ENDMETHOD.
METHOD test_1. " lo_test_object ist hier frisch initialisiert ENDMETHOD.
METHOD test_2. " lo_test_object ist wieder frisch (nicht von test_1 beeinflusst) ENDMETHOD.ENDCLASS.5. CDS View Testing
Test Environment einrichten
CLASS ltc_cds_test DEFINITION FOR TESTING DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION. CLASS-DATA environment TYPE REF TO if_cds_test_environment.
CLASS-METHODS class_setup. CLASS-METHODS class_teardown.
METHODS setup. METHODS test_customer_view FOR TESTING.ENDCLASS.
CLASS ltc_cds_test IMPLEMENTATION. METHOD class_setup. " CDS Test Environment erstellen environment = cl_cds_test_environment=>create_for_multiple_cds( i_for_entities = VALUE #( ( i_for_entity = 'Z_CUSTOMER' ) ( i_for_entity = 'Z_SALESORDER' ) ) ). ENDMETHOD.
METHOD class_teardown. environment->destroy( ). ENDMETHOD.
METHOD setup. environment->clear_doubles( ). ENDMETHOD.
METHOD test_customer_view. " GIVEN: Testdaten einfügen DATA(lt_customers) = VALUE ztab_customers( ( customer_id = '0001' name = 'Test AG' country = 'DE' ) ( customer_id = '0002' name = 'Demo GmbH' country = 'AT' ) ).
environment->insert_test_data( lt_customers ).
" WHEN: CDS View abfragen SELECT customer_id, name, country FROM z_customer WHERE country = 'DE' INTO TABLE @DATA(lt_result).
" THEN: Assertions cl_abap_unit_assert=>assert_equals( act = lines( lt_result ) exp = 1 msg = 'Should find 1 German customer' ).
cl_abap_unit_assert=>assert_equals( act = lt_result[ 1 ]-name exp = 'Test AG' ). ENDMETHOD.ENDCLASS.6. RAP Testing
RAP Business Object testen
CLASS ltc_rap_test DEFINITION FOR TESTING DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION. CLASS-DATA environment TYPE REF TO if_cds_test_environment.
CLASS-METHODS class_setup. METHODS setup. METHODS teardown.
METHODS test_create_book FOR TESTING. METHODS test_action_start_reading FOR TESTING. METHODS test_validation_isbn FOR TESTING.ENDCLASS.
CLASS ltc_rap_test IMPLEMENTATION. METHOD class_setup. environment = cl_cds_test_environment=>create_for_multiple_cds( i_for_entities = VALUE #( ( i_for_entity = 'ZI_BOOK' ) ) ). ENDMETHOD.
METHOD setup. environment->clear_doubles( ). ENDMETHOD.
METHOD teardown. ROLLBACK ENTITIES. ENDMETHOD.
METHOD test_create_book. " GIVEN DATA lt_create TYPE TABLE FOR CREATE zi_book. lt_create = VALUE #( ( %cid = 'BOOK_1' title = 'Test Book' author = 'Test Author' isbn = '1234567890' pages = 100 ) ).
" WHEN MODIFY ENTITIES OF zi_book ENTITY Book CREATE FROM lt_create MAPPED DATA(mapped) FAILED DATA(failed) REPORTED DATA(reported).
COMMIT ENTITIES.
" THEN cl_abap_unit_assert=>assert_initial( act = failed msg = 'Create should succeed' ).
" Buch lesen READ ENTITIES OF zi_book ENTITY Book ALL FIELDS WITH VALUE #( ( %cid = 'BOOK_1' ) ) RESULT DATA(lt_books).
cl_abap_unit_assert=>assert_equals( act = lt_books[ 1 ]-title exp = 'Test Book' ). ENDMETHOD.
METHOD test_action_start_reading. " GIVEN: Buch mit Status 'N' anlegen MODIFY ENTITIES OF zi_book ENTITY Book CREATE FROM VALUE #( ( %cid = 'B1' title = 'Test' status = 'N' ) ). COMMIT ENTITIES.
" WHEN: Action ausführen MODIFY ENTITIES OF zi_book ENTITY Book EXECUTE startReading FROM VALUE #( ( %cid = 'B1' ) ) RESULT DATA(result).
COMMIT ENTITIES.
" THEN: Status sollte 'R' sein READ ENTITIES OF zi_book ENTITY Book FIELDS ( status ) WITH VALUE #( ( %cid = 'B1' ) ) RESULT DATA(lt_books).
cl_abap_unit_assert=>assert_equals( act = lt_books[ 1 ]-status exp = 'R' ). ENDMETHOD.
METHOD test_validation_isbn. " GIVEN: Ungültige ISBN (zu kurz) DATA lt_create TYPE TABLE FOR CREATE zi_book. lt_create = VALUE #( ( %cid = 'B1' title = 'Test' isbn = '12345' ) " Nur 5 Zeichen! ).
" WHEN MODIFY ENTITIES OF zi_book ENTITY Book CREATE FROM lt_create.
COMMIT ENTITIES FAILED DATA(failed).
" THEN: Sollte fehlschlagen cl_abap_unit_assert=>assert_not_initial( act = failed-book msg = 'Validation should fail' ). ENDMETHOD.ENDCLASS.7. Test Doubles (Mocking)
Für abhängige Objekte:
" Interface (zu mocken)INTERFACE zif_email_sender. METHODS send_email IMPORTING iv_to TYPE string iv_subject TYPE string iv_body TYPE string RETURNING VALUE(rv_success) TYPE abap_bool.ENDINTERFACE.
" Produktiv-KlasseCLASS zcl_order_processor DEFINITION PUBLIC. PUBLIC SECTION. METHODS constructor IMPORTING io_email_sender TYPE REF TO zif_email_sender.
METHODS process_order IMPORTING iv_order_id TYPE string RETURNING VALUE(rv_success) TYPE abap_bool.
PRIVATE SECTION. DATA mo_email_sender TYPE REF TO zif_email_sender.ENDCLASS.
CLASS zcl_order_processor IMPLEMENTATION. METHOD constructor. mo_email_sender = io_email_sender. ENDMETHOD.
METHOD process_order. " Verarbeitung...
" Email versenden DATA(lv_email_sent) = mo_email_sender->send_email( iv_to = 'customer@example.com' iv_subject = 'Order Confirmation' iv_body = |Your order { iv_order_id } was processed| ).
rv_success = lv_email_sent. ENDMETHOD.ENDCLASS.
" ===== TEST mit Mock =====CLASS ltc_test DEFINITION FOR TESTING. PRIVATE SECTION. DATA mo_email_mock TYPE REF TO ltc_email_sender_mock. DATA mo_processor TYPE REF TO zcl_order_processor.
METHODS setup. METHODS test_order_processing FOR TESTING.ENDCLASS.
CLASS ltc_test IMPLEMENTATION. METHOD setup. " Mock erstellen mo_email_mock = NEW ltc_email_sender_mock( ).
" Processor mit Mock injizieren mo_processor = NEW zcl_order_processor( mo_email_mock ). ENDMETHOD.
METHOD test_order_processing. " GIVEN: Mock gibt true zurück mo_email_mock->set_return_value( abap_true ).
" WHEN DATA(lv_result) = mo_processor->process_order( '12345' ).
" THEN cl_abap_unit_assert=>assert_true( lv_result ).
" Prüfen: wurde send_email aufgerufen? cl_abap_unit_assert=>assert_true( act = mo_email_mock->was_send_email_called( ) ). ENDMETHOD.ENDCLASS.
" Mock-ImplementierungCLASS ltc_email_sender_mock DEFINITION. PUBLIC SECTION. INTERFACES zif_email_sender.
METHODS set_return_value IMPORTING iv_value TYPE abap_bool.
METHODS was_send_email_called RETURNING VALUE(rv_called) TYPE abap_bool.
PRIVATE SECTION. DATA mv_return_value TYPE abap_bool. DATA mv_was_called TYPE abap_bool.ENDCLASS.
CLASS ltc_email_sender_mock IMPLEMENTATION. METHOD zif_email_sender~send_email. mv_was_called = abap_true. rv_success = mv_return_value. ENDMETHOD.
METHOD set_return_value. mv_return_value = iv_value. ENDMETHOD.
METHOD was_send_email_called. rv_called = mv_was_called. ENDMETHOD.ENDCLASS.8. Code Coverage
Coverage messen
In ADT:
- Rechtsklick auf Klasse →
Coverage As→ABAP Unit Test - Coverage Report öffnet sich
Ziel: >70% für kritische Klassen, >80% für Business Logic
Coverage interpretieren
| Coverage | Qualität | Action |
|---|---|---|
| <50% | ❌ Schlecht | Mehr Tests schreiben! |
| 50-70% | ⚠️ Okay | Kritische Pfade testen |
| 70-90% | ✅ Gut | Edge Cases ergänzen |
| >90% | ⭐ Exzellent | Maintain |
Wichtig: 100% Coverage ≠ perfekte Tests!
9. Test-Driven Development (TDD)
Red-Green-Refactor Cycle
1. RED: Test schreiben (schlägt fehl) ↓2. GREEN: Minimalen Code schreiben (Test besteht) ↓3. REFACTOR: Code verbessern (Tests bleiben grün) ↓RepeatBeispiel TDD-Session
1. RED: Test schreiben
METHOD test_calculate_discount. " Given DATA(lv_amount) = 1000.
" When DATA(lv_discount) = zcl_pricing=>calculate_discount( lv_amount ).
" Then cl_abap_unit_assert=>assert_equals( act = lv_discount exp = 100 " 10% Rabatt ).ENDMETHOD.Test ausführen: ❌ Fails (Methode existiert nicht)
2. GREEN: Minimal-Implementierung
CLASS zcl_pricing IMPLEMENTATION. METHOD calculate_discount. rv_discount = iv_amount * '0.1'. ENDMETHOD.ENDCLASS.Test ausführen: ✅ Passes
3. REFACTOR: Verbessern
METHOD calculate_discount. CONSTANTS lc_discount_rate TYPE p LENGTH 3 DECIMALS 2 VALUE '0.1'.
IF iv_amount <= 0. RAISE EXCEPTION TYPE zcx_invalid_amount. ENDIF.
rv_discount = iv_amount * lc_discount_rate.ENDMETHOD.Tests ausführen: ✅ Noch grün!
4. Neuer Test: Edge Case
METHOD test_discount_negative_amount. TRY. zcl_pricing=>calculate_discount( -100 ). cl_abap_unit_assert=>fail( 'Exception expected' ). CATCH zcx_invalid_amount. " Expected ENDTRY.ENDMETHOD.10. Best Practices
✅ DO
- Test-First schreiben (TDD)
- Ein Assert pro Test (Single Responsibility)
- Sprechende Test-Namen (
test_discount_for_premium_customer) - Given-When-Then Pattern nutzen
- Unabhängige Tests (keine Reihenfolge-Abhängigkeit)
- Schnelle Tests (<1s pro Test)
- Realistische Testdaten (aber anonymisiert)
❌ DON’T
- Produkt-Daten testen (nur Test-Daten!)
- Sleep/Wait verwenden
- DB-Commits in Tests (wenn vermeidbar)
- Tests deaktivieren (Commented-Out)
- Komplexe Test-Logik (Tests sollen einfach sein)
🎯 Zusammenfassung
ABAP Unit Testing Cheat Sheet:
| Aspekt | Tool/Pattern |
|---|---|
| Basis-Tests | cl_abap_unit_assert |
| CDS Views | cl_cds_test_environment |
| RAP | MODIFY/READ ENTITIES + COMMIT ENTITIES |
| Mocking | Test Doubles (Custom Mock-Klassen) |
| Coverage | ADT Coverage Tool (Ziel: >70%) |
| Pattern | Given-When-Then |
| Approach | Test-Driven Development (TDD) |
Mindset: Tests sind Investition, keine Verschwendung!
Siehe auch:
Happy Testing! ✅