ABAP Unit is the integrated test framework for automated unit tests in ABAP. It enables test-driven development (TDD) and ensures code quality through repeatable, automated tests.
Basic Concept
- Test classes are local classes with the
FOR TESTINGaddition - Test methods are methods with the
FOR TESTINGaddition - Assertions verify expected results using
CL_ABAP_UNIT_ASSERT - Tests are executed via CTRL+SHIFT+F10 or the context menu
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 logic with assertions cl_abap_unit_assert=>assert_equals( act = actual_value exp = expected_value ). ENDMETHOD.ENDCLASS.Examples
1. Simple Unit Test
" Class under testCLASS 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.
" Test classCLASS 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. " Executed before each test 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 of positive numbers failed' ). 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. Important Assertion Methods
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. " Compare values cl_abap_unit_assert=>assert_equals( act = 42 exp = 42 msg = 'Values are not equal' ).
" Compare structures 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 ).
" Compare tables 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.
" Check if true cl_abap_unit_assert=>assert_true( act = lv_flag msg = 'Flag should be true' ).
" Check if false cl_abap_unit_assert=>assert_false( act = xsdbool( 1 = 2 ) msg = 'Expression should be false' ). ENDMETHOD.
METHOD test_assert_initial. DATA: lv_empty TYPE string, lv_filled TYPE string VALUE 'Test'.
" Check if initial (empty) cl_abap_unit_assert=>assert_initial( act = lv_empty msg = 'Variable should be initial' ).
" Check if NOT initial cl_abap_unit_assert=>assert_not_initial( act = lv_filled msg = 'Variable should not be initial' ). ENDMETHOD.
METHOD test_assert_bound. DATA: lo_object TYPE REF TO lcl_calculator, lo_null TYPE REF TO lcl_calculator.
lo_object = NEW #( ).
" Check if reference is bound cl_abap_unit_assert=>assert_bound( act = lo_object msg = 'Object reference should be bound' ).
" Check if NOT bound cl_abap_unit_assert=>assert_not_bound( act = lo_null msg = 'Object reference should be NULL' ). ENDMETHOD.
METHOD test_assert_differs. " Check that values are different cl_abap_unit_assert=>assert_differs( act = 'ABC' exp = 'XYZ' msg = 'Values should be different' ). ENDMETHOD.
METHOD test_assert_char_cp. " Pattern matching (like CP in IF) cl_abap_unit_assert=>assert_char_cp( act = 'Hello World' exp = 'Hello*' msg = 'String should start with Hello' ). ENDMETHOD.
METHOD test_fail. " Intentionally fail a test IF 1 = 2. cl_abap_unit_assert=>fail( msg = 'This code should never be reached' ). ENDIF. ENDMETHOD.ENDCLASS.3. Setup and 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, " Once before all tests class_teardown. " Once after all tests
METHODS: setup, " Before each test teardown, " After each test test_first FOR TESTING, test_second FOR TESTING.ENDCLASS.
CLASS ltc_lifecycle IMPLEMENTATION. METHOD class_setup. " One-time initialization for all tests " e.g., create test data in DB gv_class_setup_done = abap_true. ENDMETHOD.
METHOD class_teardown. " Cleanup after all tests " e.g., delete test data from DB ENDMETHOD.
METHOD setup. " Before EACH test case " e.g., instantiate object anew mv_setup_done = abap_true. ENDMETHOD.
METHOD teardown. " After EACH test case " e.g., reset variables 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. " Should not throw an exception TRY. mo_cut->validate_age( 25 ). CATCH cx_parameter_invalid. cl_abap_unit_assert=>fail( 'Unexpected exception' ). ENDTRY. ENDMETHOD.
METHOD test_negative_age_raises. " Exception is expected TRY. mo_cut->validate_age( -5 ). cl_abap_unit_assert=>fail( 'Exception expected' ). CATCH cx_parameter_invalid INTO DATA(lx_error). " Expected exception - test successful cl_abap_unit_assert=>assert_equals( act = lx_error->parameter exp = 'AGE' ). ENDTRY. ENDMETHOD.ENDCLASS.5. Test Doubles (Mocking)
" Interface for Dependency InjectionINTERFACE lif_database. METHODS: get_customer IMPORTING iv_id TYPE i RETURNING VALUE(rs_customer) TYPE ty_customer.ENDINTERFACE.
" Productive implementationCLASS lcl_database DEFINITION. PUBLIC SECTION. INTERFACES: lif_database.ENDCLASS.
" Class under testCLASS 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. " Returns mock data instead of DB access rs_customer = ms_mock_customer. ENDMETHOD.ENDCLASS.
" Test class with 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. " Create mock mo_mock_db = NEW #( ). mo_mock_db->ms_mock_customer = VALUE #( id = 1 name = 'Test Customer' ).
" Inject mock into CUT 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 Customer' ). ENDMETHOD.ENDCLASS.6. Test Attributes
CLASS ltc_attributes DEFINITION FOR TESTING DURATION MEDIUM " SHORT | MEDIUM | LONG RISK LEVEL DANGEROUS. " HARMLESS | DANGEROUS | CRITICAL
PRIVATE SECTION. METHODS: " Categorization test_quick FOR TESTING,
" Skip test test_not_yet_implemented FOR TESTING.ENDCLASS.
CLASS ltc_attributes IMPLEMENTATION. METHOD test_quick. " Normal test cl_abap_unit_assert=>assert_true( abap_true ). ENDMETHOD.
METHOD test_not_yet_implemented. " Mark test as "not yet implemented" cl_abap_unit_assert=>skip( 'Not yet implemented' ). ENDMETHOD.ENDCLASS.7. Test Data with Helper Methods
CLASS ltc_with_helpers DEFINITION FOR TESTING DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION. METHODS: test_process_orders FOR TESTING, " Helper methods (not 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 for Private Methods
" Class under testCLASS lcl_processor DEFINITION FRIENDS ltc_processor. " Declare test class as friend
PUBLIC SECTION. METHODS: process RETURNING VALUE(rv_result) TYPE string.
PRIVATE SECTION. METHODS: calculate_internal RETURNING VALUE(rv_value) TYPE i.ENDCLASS.
" Test class can access private methodsCLASS 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. " Direct access to private method through 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. " Create test environment for DB tables mo_environment = cl_osql_test_environment=>create( i_dependency_list = VALUE #( ( 'KNA1' ) ) ). ENDMETHOD.
METHOD class_teardown. mo_environment->destroy( ). ENDMETHOD.
METHOD setup. " Insert test data DATA: lt_kna1 TYPE TABLE OF kna1.
lt_kna1 = VALUE #( ( mandt = sy-mandt kunnr = '0000001000' name1 = 'Test Customer' ) ).
mo_environment->insert_test_data( lt_kna1 ). ENDMETHOD.
METHOD test_read_customer. " SELECT now reads from Test Double instead of real DB SELECT SINGLE name1 FROM kna1 WHERE kunnr = '0000001000' INTO @DATA(lv_name).
cl_abap_unit_assert=>assert_equals( act = lv_name exp = 'Test Customer' ). ENDMETHOD.ENDCLASS.Test Execution
" In Eclipse/ADT:CTRL + SHIFT + F10 " Execute all testsRight-click → Run As → ABAP Unit Test
" In SE80:Right-click → Execute Unit TestDURATION and RISK LEVEL
| DURATION | Expected Runtime |
|---|---|
| SHORT | < 1 second |
| MEDIUM | < 5 seconds |
| LONG | > 5 seconds |
| RISK LEVEL | Description |
|---|---|
| HARMLESS | No database changes |
| DANGEROUS | Possible test data changes |
| CRITICAL | Production data could be affected |
Important Notes / Best Practice
- Test classes in local classes (definition/implementation at the end of the class).
- Naming:
ltc_for test classes,ltd_for Test Doubles,test_for methods. - AAA-Pattern: Arrange (Given), Act (When), Assert (Then).
- One assert per test - focused tests are more maintainable.
- Setup/Teardown for repeated initialization/cleanup.
- Dependency Injection for testable architecture with mocks.
- FRIENDS enables tests of private methods (use sparingly).
cl_abap_unit_assert=>skip()for not yet implemented tests.- SQL Test Double Framework for database-independent tests.
- Run tests regularly - ideally in a CI/CD pipeline.