Unit Testing is more important than ever in ABAP Cloud. This guide shows you how to write production-ready, tested code.
Why Unit Testing in ABAP Cloud?
Advantages
- Early error detection (before production)
- Refactoring safety (changes without fear)
- Documentation (tests = specification)
- CI/CD-ready (automated tests)
- Code quality (enforces testable code)
ABAP Cloud Specifics
- No debugger in production -> Tests essential
- RAP Framework -> Special test tools
- CDS Views -> CDS Test Environment
1. ABAP Unit Basics
First Test Class
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 CLASS =====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.Running Tests
In ADT:
- Right-click on class ->
Run As->ABAP Unit Test - Results in
ABAP Unittab
All tests green? Perfect!
2. Test Structure: Given-When-Then
Best Practice Pattern:
METHOD test_something. " GIVEN (Arrange) " - Setup test data " - Create preconditions
DATA(lv_input) = 'Test'. DATA(lo_object) = NEW zcl_my_class( ).
" WHEN (Act) " - Execute method being tested
DATA(lv_result) = lo_object->process( lv_input ).
" THEN (Assert) " - Check result
cl_abap_unit_assert=>assert_equals( act = lv_result exp = 'EXPECTED' ).ENDMETHOD.3. Assertion Methods
Check Equality
" Value equalitycl_abap_unit_assert=>assert_equals( act = lv_actual exp = lv_expected msg = 'Values should match').
" Object instantiationcl_abap_unit_assert=>assert_bound( act = lo_object msg = 'Object should be instantiated').
" Non-equalitycl_abap_unit_assert=>assert_differs( act = lv_actual exp = lv_wrong_value).Boolean Checks
" 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).Table Checks
" Table containscl_abap_unit_assert=>assert_table_contains( table = lt_result line = ls_expected_line).
" Number of rowsDATA(lv_lines) = lines( lt_result ).cl_abap_unit_assert=>assert_equals( act = lv_lines exp = 3).Check Exceptions
METHOD test_exception_thrown. DATA(lo_object) = NEW zcl_my_class( ).
TRY. lo_object->method_that_throws( 'invalid' ).
" If we get here: Test failed! cl_abap_unit_assert=>fail( msg = 'Exception should have been thrown' ).
CATCH zcx_my_exception INTO DATA(lx_error). " Expected 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 (once for all tests) CLASS-DATA lo_shared_object TYPE REF TO zcl_my_class.
CLASS-METHODS class_setup. CLASS-METHODS class_teardown.
" Instance-Level (before/after each 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. " Executed 1x before all tests lo_shared_object = NEW zcl_my_class( ). ENDMETHOD.
METHOD class_teardown. " Executed 1x after all tests FREE lo_shared_object. ENDMETHOD.
METHOD setup. " Executed before EACH test lo_test_object = NEW zcl_my_class( ). ENDMETHOD.
METHOD teardown. " Executed after EACH test CLEAR lo_test_object. ENDMETHOD.
METHOD test_1. " lo_test_object is freshly initialized here ENDMETHOD.
METHOD test_2. " lo_test_object is fresh again (not affected by test_1) ENDMETHOD.ENDCLASS.5. CDS View Testing
Set Up Test Environment
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. " Create CDS Test Environment 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: Insert test data 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: Query CDS View 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
Test RAP Business Object
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' ).
" Read book 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: Create book with Status 'N' MODIFY ENTITIES OF zi_book ENTITY Book CREATE FROM VALUE #( ( %cid = 'B1' title = 'Test' status = 'N' ) ). COMMIT ENTITIES.
" WHEN: Execute action MODIFY ENTITIES OF zi_book ENTITY Book EXECUTE startReading FROM VALUE #( ( %cid = 'B1' ) ) RESULT DATA(result).
COMMIT ENTITIES.
" THEN: Status should be 'R' 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: Invalid ISBN (too short) DATA lt_create TYPE TABLE FOR CREATE zi_book. lt_create = VALUE #( ( %cid = 'B1' title = 'Test' isbn = '12345' ) " Only 5 characters! ).
" WHEN MODIFY ENTITIES OF zi_book ENTITY Book CREATE FROM lt_create.
COMMIT ENTITIES FAILED DATA(failed).
" THEN: Should fail cl_abap_unit_assert=>assert_not_initial( act = failed-book msg = 'Validation should fail' ). ENDMETHOD.ENDCLASS.7. Test Doubles (Mocking)
For dependent objects:
" Interface (to mock)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.
" Production classCLASS 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. " Processing...
" Send email DATA(lv_email_sent) = mo_email_sender->send_email( iv_subject = 'Order Confirmation' iv_body = |Your order { iv_order_id } was processed| ).
rv_success = lv_email_sent. ENDMETHOD.ENDCLASS.
" ===== TEST with 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. " Create mock mo_email_mock = NEW ltc_email_sender_mock( ).
" Inject mock into processor mo_processor = NEW zcl_order_processor( mo_email_mock ). ENDMETHOD.
METHOD test_order_processing. " GIVEN: Mock returns true 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 ).
" Check: was send_email called? cl_abap_unit_assert=>assert_true( act = mo_email_mock->was_send_email_called( ) ). ENDMETHOD.ENDCLASS.
" Mock implementationCLASS 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
Measure Coverage
In ADT:
- Right-click on class ->
Coverage As->ABAP Unit Test - Coverage report opens
Goal: >70% for critical classes, >80% for business logic
Interpret Coverage
| Coverage | Quality | Action |
|---|---|---|
| <50% | Bad | Write more tests! |
| 50-70% | Okay | Test critical paths |
| 70-90% | Good | Add edge cases |
| >90% | Excellent | Maintain |
Important: 100% coverage ≠ perfect tests!
9. Test-Driven Development (TDD)
Red-Green-Refactor Cycle
1. RED: Write test (fails) |2. GREEN: Write minimal code (test passes) |3. REFACTOR: Improve code (tests stay green) |RepeatExample TDD Session
1. RED: Write test
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% discount ).ENDMETHOD.Run test: Fails (method doesn’t exist)
2. GREEN: Minimal implementation
CLASS zcl_pricing IMPLEMENTATION. METHOD calculate_discount. rv_discount = iv_amount * '0.1'. ENDMETHOD.ENDCLASS.Run test: Passes
3. REFACTOR: Improve
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.Run tests: Still green!
4. New 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
- Write test-first (TDD)
- One assert per test (Single Responsibility)
- Descriptive test names (
test_discount_for_premium_customer) - Use Given-When-Then pattern
- Independent tests (no order dependency)
- Fast tests (<1s per test)
- Realistic test data (but anonymized)
DON’T
- Test production data (only test data!)
- Use sleep/wait
- DB commits in tests (if avoidable)
- Disable tests (commented-out)
- Complex test logic (tests should be simple)
Summary
ABAP Unit Testing Cheat Sheet:
| Aspect | Tool/Pattern |
|---|---|
| Basic tests | cl_abap_unit_assert |
| CDS Views | cl_cds_test_environment |
| RAP | MODIFY/READ ENTITIES + COMMIT ENTITIES |
| Mocking | Test Doubles (custom mock classes) |
| Coverage | ADT Coverage Tool (Goal: >70%) |
| Pattern | Given-When-Then |
| Approach | Test-Driven Development (TDD) |
Mindset: Tests are investment, not waste!
See also:
- RAP Tutorial Part 3: Best Practices
- ABAP Cloud Performance Optimization
- ABAP Cloud Cheat Sheet
Happy Testing!