ABAP Unit Testing in ABAP Cloud: Komplettanleitung (2025)

kategorie
Testing
Veröffentlicht
autor
Johannes

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:

  1. Rechtsklick auf Klasse → Run AsABAP Unit Test
  2. Ergebnisse im ABAP Unit Tab

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-Gleichheit
cl_abap_unit_assert=>assert_equals(
act = lv_actual
exp = lv_expected
msg = 'Values should match'
).
" Objekt-Gleichheit
cl_abap_unit_assert=>assert_bound(
act = lo_object
msg = 'Object should be instantiated'
).
" Nicht-Gleichheit
cl_abap_unit_assert=>assert_differs(
act = lv_actual
exp = lv_wrong_value
).

Boole’sche Prüfungen

" True/False
cl_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öße
cl_abap_unit_assert=>assert_table_contains(
table = lt_result
line = ls_expected_line
).
" Anzahl Zeilen
DATA(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-Klasse
CLASS 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-Implementierung
CLASS 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:

  1. Rechtsklick auf Klasse → Coverage AsABAP Unit Test
  2. Coverage Report öffnet sich

Ziel: >70% für kritische Klassen, >80% für Business Logic

Coverage interpretieren

CoverageQualitätAction
<50%❌ SchlechtMehr Tests schreiben!
50-70%⚠️ OkayKritische Pfade testen
70-90%✅ GutEdge Cases ergänzen
>90%⭐ ExzellentMaintain

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

Beispiel 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

  1. Test-First schreiben (TDD)
  2. Ein Assert pro Test (Single Responsibility)
  3. Sprechende Test-Namen (test_discount_for_premium_customer)
  4. Given-When-Then Pattern nutzen
  5. Unabhängige Tests (keine Reihenfolge-Abhängigkeit)
  6. Schnelle Tests (<1s pro Test)
  7. Realistische Testdaten (aber anonymisiert)

❌ DON’T

  1. Produkt-Daten testen (nur Test-Daten!)
  2. Sleep/Wait verwenden
  3. DB-Commits in Tests (wenn vermeidbar)
  4. Tests deaktivieren (Commented-Out)
  5. Komplexe Test-Logik (Tests sollen einfach sein)

🎯 Zusammenfassung

ABAP Unit Testing Cheat Sheet:

AspektTool/Pattern
Basis-Testscl_abap_unit_assert
CDS Viewscl_cds_test_environment
RAPMODIFY/READ ENTITIES + COMMIT ENTITIES
MockingTest Doubles (Custom Mock-Klassen)
CoverageADT Coverage Tool (Ziel: >70%)
PatternGiven-When-Then
ApproachTest-Driven Development (TDD)

Mindset: Tests sind Investition, keine Verschwendung!


Siehe auch:

Happy Testing!