ABAP Unit - Legacy objects
This article is about so-called legacy code and how you can use ABAP Unit there too. However, we can only recommend this type to a limited extent.
The term legacy object sounds a bit derogatory when it comes to reports and function modules, but it is also about the possibilities that you have during the test. More on this in this article.
Do you still have an old report in the system that is often revised by you and you therefore want to implement a few tests so that future developments can be implemented in a stable manner? Implementing a test class in a report is not a problem, but you should keep two important points in mind:
- Spaghetti code cannot be tested because it does not provide any callable subroutines
- Forms or methods should have proper interfaces and be based on as little global data as possible
So if you have already implemented clean subroutines (methods or forms), it is not a problem to implement a test class. Two examples of a report that do the same in terms of content. Company code data is read, recorded in a table and then output using CL_DEMO_OUTPUT.
The first example with an implementation as a local class and a test class:
REPORT ztest_report_with_class. *** Selection screen PARAMETERS: p_bukrs TYPE t001-bukrs. *** Report logic CLASS lcl_prog DEFINITION FINAL. PUBLIC SECTION. METHODS: main, select_company_code IMPORTING id_bukrs TYPE bukrs RETURNING VALUE(rs_company_data) TYPE t001. ENDCLASS. CLASS lcl_prog IMPLEMENTATION. METHOD main. DATA: lt_company_data TYPE STANDARD TABLE OF t001. INSERT select_company_code( p_bukrs ) INTO TABLE lt_company_data. cl_demo_output=>display_data( lt_company_data ). ENDMETHOD. METHOD select_company_code. SELECT SINGLE * FROM t001 WHERE bukrs = @id_bukrs INTO @rs_company_data. ENDMETHOD. ENDCLASS. *** Local testclass CLASS ltc_prog DEFINITION FINAL FOR TESTING DURATION SHORT RISK LEVEL HARMLESS. PRIVATE SECTION. METHODS: select_existing_company_code FOR TESTING RAISING cx_static_check. ENDCLASS. CLASS ltc_prog IMPLEMENTATION. METHOD select_existing_company_code. DATA(lo_cut) = NEW lcl_prog( ). DATA(ls_found) = lo_cut->select_company_code( '4711' ). cl_abap_unit_assert=>assert_not_initial( ls_found ). ENDMETHOD. ENDCLASS. *** Report START-OF-SELECTION. NEW lcl_prog( )->main( ).
The second example with classic FORM routines and a test class:
REPORT ztest_report_with_forms. *** Selection screen PARAMETERS: p_bukrs TYPE t001-bukrs. *** Report START-OF-SELECTION. PERFORM main. *** Report logic FORM main. DATA: lt_company_data TYPE STANDARD TABLE OF t001, ls_company_data TYPE t001. PERFORM select_company_code USING p_bukrs CHANGING ls_company_data. INSERT ls_company_data INTO TABLE lt_company_data. cl_demo_output=>display_data( lt_company_data ). ENDFORM. FORM select_company_code USING p_bukrs TYPE t001-bukrs CHANGING cs_company_code TYPE t001. SELECT SINGLE * FROM t001 WHERE bukrs = @p_bukrs INTO @cs_company_code. ENDFORM. *** Local testclass CLASS ltc_prog DEFINITION FINAL FOR TESTING DURATION SHORT RISK LEVEL HARMLESS. PRIVATE SECTION. METHODS: select_existing_company_code FOR TESTING RAISING cx_static_check. ENDCLASS. CLASS ltc_prog IMPLEMENTATION. METHOD select_existing_company_code. DATA: ls_found TYPE t001. PERFORM select_company_code USING '4711' CHANGING ls_found. cl_abap_unit_assert=>assert_not_initial( ls_found ). ENDMETHOD. ENDCLASS.
Function modules can be tested by you in the same way and are almost even better suited than a report. This is due to the encapsulation of the data and the interface that a function module brings with it from the start. If you look into the main include of a function group, you will already find many includes commented out that are intended for different things.
The include with the ending T99 can be used to test the function modules of the function group. To do this, we create a simple function module in the system that is supposed to carry out a simple calculation for us.
FUNCTION z_bs_demo_calculate_two_number IMPORTING VALUE(id_number_one) TYPE i VALUE(id_number_two) TYPE i EXPORTING VALUE(ed_result) TYPE i. ed_result = id_number_one + id_number_two. ENDFUNCTION.
Then we create the include and implement the test class, which then calls the function module. In this case we have only created one test method as an example.
CLASS ltc_function_modules DEFINITION FINAL FOR TESTING DURATION SHORT RISK LEVEL HARMLESS. PRIVATE SECTION. METHODS: calculate_1_and_5 FOR TESTING. ENDCLASS. CLASS ltc_function_modules IMPLEMENTATION. METHOD calculate_1_and_5. DATA: ld_result TYPE i. CALL FUNCTION 'Z_BS_DEMO_CALCULATE_TWO_NUMBER' EXPORTING id_number_one = 1 id_number_two = 5 IMPORTING ed_result = ld_result. cl_abap_unit_assert=>assert_equals( act = ld_result exp = 6 ). ENDMETHOD. ENDCLASS.
This makes it easy to equip the function modules, classes and forms within a function group with tests and to test them with every change.
There are still small restrictions for “legacy code”, because not all of the techniques that we deal with in this book can be used. For example, no SEAMs are available.
A little tip from our side for you is therefore to carry out all developments in global classes and only use function modules and reports as covers for the call, so that the majority of the logic is neatly wrapped in the OO and you can enjoy full efficiency with ABAP Unit. We will tell you more about this in the architecture article of this series.
You can also write ABAP unit tests for old objects or legacy code, but the code must meet certain requirements for this to be possible. But the more unit tests you have in the system and thus a high level of coverage, the more likely it is that you will notice problems with adjustments.