ABAP Unit Testing: Test-Driven Development

Category
ABAP-Statements
Published
Author
Johannes

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 TESTING addition
  • Test methods are methods with the FOR TESTING addition
  • 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 test
CLASS 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 class
CLASS 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 Injection
INTERFACE lif_database.
METHODS: get_customer IMPORTING iv_id TYPE i
RETURNING VALUE(rs_customer) TYPE ty_customer.
ENDINTERFACE.
" Productive implementation
CLASS lcl_database DEFINITION.
PUBLIC SECTION.
INTERFACES: lif_database.
ENDCLASS.
" Class under test
CLASS 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 Mock
CLASS 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 test
CLASS 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 methods
CLASS 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 tests
Right-click → Run As → ABAP Unit Test
" In SE80:
Right-click → Execute Unit Test

DURATION and RISK LEVEL

DURATIONExpected Runtime
SHORT< 1 second
MEDIUM< 5 seconds
LONG> 5 seconds
RISK LEVELDescription
HARMLESSNo database changes
DANGEROUSPossible test data changes
CRITICALProduction 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.